From abb453e7ed53be628b730231fe76bca558d2b048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 09:11:39 -0500 Subject: [PATCH 01/38] Selenium test mixin + move some tests to selenium from Cypress --- .circleci/config.yml | 53 +++- package.json | 7 +- tests/cypress/dash/v_copy_paste.py | 106 ------- tests/cypress/dash/v_markdown.py | 51 ---- tests/cypress/tests/server/copy_paste_test.ts | 271 ------------------ tests/cypress/tests/server/dash_test.ts | 9 - tests/selenium/conftest.py | 162 +++++++++++ tests/selenium/test_basic_copy_paste.py | 200 +++++++++++++ tests/selenium/test_basic_operations.py | 46 +++ tests/selenium/test_markdown_copy_paste.py | 87 ++++++ 10 files changed, 549 insertions(+), 443 deletions(-) delete mode 100644 tests/cypress/dash/v_copy_paste.py delete mode 100644 tests/cypress/dash/v_markdown.py delete mode 100644 tests/cypress/tests/server/copy_paste_test.ts create mode 100644 tests/selenium/conftest.py create mode 100644 tests/selenium/test_basic_copy_paste.py create mode 100644 tests/selenium/test_basic_operations.py create mode 100644 tests/selenium/test_markdown_copy_paste.py diff --git a/.circleci/config.yml b/.circleci/config.yml index fa94dacf3..61ea6d32c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,58 @@ version: 2 jobs: + "legacy-server-test": + docker: + - image: circleci/python:3.6.9-node-browsers + - image: cypress/base:10 + + steps: + - checkout + - restore_cache: + key: deps1-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} + - run: + name: Install npm packages + command: npm ci + - run: + name: Cypress Install + command: | + $(npm bin)/cypress install + + - save_cache: + key: deps1-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} + paths: + - node_modules + - /home/circleci/.cache/Cypress + + - run: + name: Install requirements + command: | + sudo pip install --upgrade virtualenv + python -m venv venv || virtualenv venv + . venv/bin/activate + pip install -r dev-requirements.txt --quiet + git clone --depth 1 git@github.com:plotly/dash.git dash-main + pip install -e ./dash-main[dev] --quiet + cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. + + - run: + name: Build + command: | + . venv/bin/activate + npm run private::build:js-test + npm run private::build:py + pip install -e . + + - run: + name: Run tests + command: | + . venv/bin/activate + npm run test.server-legacy + + "server-test": docker: - - image: circleci/python:3.6-node-browsers + - image: circleci/python:3.6.9-node-browsers - image: cypress/base:10 steps: @@ -33,6 +82,7 @@ jobs: pip install -r dev-requirements.txt --quiet git clone --depth 1 git@github.com:plotly/dash.git dash-main pip install -e ./dash-main[dev] --quiet + cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. - run: name: Build @@ -247,6 +297,7 @@ workflows: jobs: - "python-3.6" - "node" + - "legacy-server-test" - "server-test" - "standalone-test" - "unit-test" diff --git a/package.json b/package.json index 3799c31b8..bd95c2598 100644 --- a/package.json +++ b/package.json @@ -23,22 +23,18 @@ "private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"", "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", "private::host_dash8081": "python tests/cypress/dash/v_be_page.py", - "private::host_dash8082": "python tests/cypress/dash/v_copy_paste.py", "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", "private::host_dash8084": "python tests/cypress/dash/v_data_loading.py", "private::host_dash8085": "python tests/cypress/dash/v_default.py", "private::host_dash8086": "python tests/cypress/dash/v_pagination.py", - "private::host_dash8087": "python tests/cypress/dash/v_markdown.py", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", "private::wait_dash8081": "wait-on http://localhost:8081", - "private::wait_dash8082": "wait-on http://localhost:8082", "private::wait_dash8083": "wait-on http://localhost:8083", "private::wait_dash8084": "wait-on http://localhost:8084", "private::wait_dash8085": "wait-on http://localhost:8085", "private::wait_dash8086": "wait-on http://localhost:8086", - "private::wait_dash8087": "wait-on http://localhost:8087", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", @@ -50,7 +46,8 @@ "postbuild": "es-check es5 dash_table/*.js", "format": "run-s \"private::lint:ts -- --fix\"", "lint": "run-s private::lint:*", - "test.server": "run-p --race private::host* private::test.server", + "test.server-legacy": "run-p --race private::host* private::test.server", + "test.server": "pytest tests/selenium", "test.standalone": "run-p --race private::host_js private::test.standalone", "test.unit": "run-s private::test.python private::test.unit", "test.visual": "build-storybook && percy-storybook", diff --git a/tests/cypress/dash/v_copy_paste.py b/tests/cypress/dash/v_copy_paste.py deleted file mode 100644 index 4dc7e6650..000000000 --- a/tests/cypress/dash/v_copy_paste.py +++ /dev/null @@ -1,106 +0,0 @@ -# pylint: disable=global-statement -import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate -import dash_html_components as html -import os -import pandas as pd -import sys - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(x) for x in module_names] -dash_table = modules[0] - -url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" -df = pd.read_csv(url) -df = df.values - -app = dash.Dash() -app.css.config.serve_locally = True -app.scripts.config.serve_locally = True - -app.layout = html.Div( - [ - html.Div(id="container", children="Hello World"), - dash_table.DataTable( - id="table", - data=df[0:250], - columns=[ - {"id": 0, "name": "Complaint ID", "hideable": True}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True, - - ), - dash_table.DataTable( - id="table2", - data=df[0:10], - columns=[ - {"id": 0, "name": "Complaint ID", "hideable": True}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True, - ), - ] -) - - -@app.callback( - Output("table", "data"), - [Input("table", "data_timestamp")], - [State("table", "data"), State("table", "data_previous")], -) -# pylint: disable=unused-argument -def updateData(timestamp, current, previous): - # pylint: enable=unused-argument - if timestamp is None or current is None or previous is None: - raise PreventUpdate - - modified = False - if len(current) == len(previous): - for (i, datum) in enumerate(current): - previous_datum = previous[i] - if datum[0] != previous_datum[0]: - modified = True - datum[1] = "MODIFIED" - - if not modified: - raise PreventUpdate - - return current - -if __name__ == "__main__": - app.run_server(port=8082, debug=False) diff --git a/tests/cypress/dash/v_markdown.py b/tests/cypress/dash/v_markdown.py deleted file mode 100644 index 6e0dc4d79..000000000 --- a/tests/cypress/dash/v_markdown.py +++ /dev/null @@ -1,51 +0,0 @@ -# pylint: disable=global-statement -import dash -import dash_html_components as html -import os -import pandas as pd -import sys - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(x) for x in module_names] -dash_table = modules[0] - -url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" -df = pd.read_csv(url) - -df['Complaint ID'] = df['Complaint ID'].map(lambda x: '**' + str(x) + '**') -df['Product'] = df['Product'].map(lambda x: '[' + str(x) + '](plot.ly)') -df['Issue'] = df['Issue'].map(lambda x: '![' + str(x) + '](https://dash.plot.ly/assets/images/logo.png)') -df['State'] = df['State'].map(lambda x: '```python\n"{}"\n```'.format(x)) - -df = df.values - -app = dash.Dash() - -app.layout = html.Div( - [ - html.Div(id="container", children="Hello World"), - dash_table.DataTable( - id="table", - data=df[0:250], - columns=[ - {"id": 1, "name": "Complaint ID", "presentation": "markdown"}, - {"id": 2, "name": "Product", "presentation": "markdown"}, - {"id": 3, "name": "Sub-product"}, - {"id": 4, "name": "Issue", "presentation": "markdown"}, - {"id": 5, "name": "Sub-issue"}, - {"id": 6, "name": "State", "presentation": "markdown"}, - {"id": 7, "name": "ZIP"} - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True - ) - ] -) - -app.run_server(debug=False, port=8087) diff --git a/tests/cypress/tests/server/copy_paste_test.ts b/tests/cypress/tests/server/copy_paste_test.ts deleted file mode 100644 index b18e19a6f..000000000 --- a/tests/cypress/tests/server/copy_paste_test.ts +++ /dev/null @@ -1,271 +0,0 @@ -import DashTable, { State } from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; - -describe('copy paste', () => { - beforeEach(() => { - cy.visit('http://localhost:8082'); - cy.wait(1000); - }); - - it('can copy multiple rows', () => { - DashTable.getCell(0, 0).click(); - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(2, 0).click(); - - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(3, 0).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 0, State.Any).click(); - - for (let row = 0; row <= 2; ++row) { - DashTable.getCell(row + 3, 0, State.Any).within(() => cy.get('.dash-cell-value').should('have.html', `${row}`)); - } - }); - - it('can copy rows 9 and 10', () => { - DashTable.getCell(9, 0).click(); - DOM.focused.type(`${Key.Shift}${Key.ArrowDown}`); - - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(1, 0).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 0, State.Any).click(); - - DashTable.getCell(1, 0, State.Any).within(() => cy.get('.dash-cell-value').should('have.html', '9')); - DashTable.getCell(2, 0, State.Any).within(() => cy.get('.dash-cell-value').should('have.html', '10')); - - }); - - it('can copy multiple rows and columns', () => { - DashTable.getCell(0, 1).click(); - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(2, 2).click(); - - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(3, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 0, State.Any).click(); - - for (let row = 0; row <= 2; ++row) { - for (let column = 1; column <= 2; ++column) { - let initialValue: string; - - DashTable.getCell(row, column, State.Any).within(() => cy.get('.dash-cell-value').then($cells => initialValue = $cells[0].innerHTML)); - DashTable.getCell(row + 3, column, State.Any).within(() => cy.get('.dash-cell-value').should('have.html', initialValue)); - } - } - }); - - it('can copy multiple rows and columns from one table and paste to another', () => { - DashTable.getCell(10, 0).click(); - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(13, 3).click(); - - DOM.focused.type(`${Key.Meta}c`); - cy.get(`#table2 tbody tr td.column-${0}`).eq(0).click(); - DOM.focused.type(`${Key.Meta}v`); - cy.get(`#table2 tbody tr td.column-${3}`).eq(3).click(); - - cy.wait(1000); - - DashTable.getCell(14, 0).click(); - DOM.focused.type(Key.Shift, { release: false }); - - for (let row = 10; row <= 13; ++row) { - for (let column = 0; column <= 3; ++column) { - let initialValue: string; - - DashTable.getCell(row, column).within(() => cy.get('.dash-cell-value').then($cells => initialValue = $cells[0].innerHTML)); - cy.get(`#table2 tbody tr td.column-${column}`).eq(row - 10).within(() => cy.get('.dash-cell-value').should('have.html', initialValue)); - } - } - }); - - describe('copy and paste with hideable columns', () => { - it('copy multiple rows and columns within table', () => { - DashTable.hideColumnById(0, '0'); - - DashTable.getCell(0, 0).click(); - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(2, 2).click(); - - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(3, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(5, 3, State.Any).click(); - - DashTable.getCell(6, 0, State.Any).click(); - DOM.focused.type(Key.Shift, { release: false }); - - for (let row = 0; row <= 2; ++row) { - for (let column = 0; column <= 2; ++column) { - let initialValue: string; - - DashTable.getCell(row, column, State.Any).within(() => cy.get('.dash-cell-value').then($cells => initialValue = $cells[0].innerHTML)); - DashTable.getCell(row + 3, column + 1, State.Any).within(() => cy.get('.dash-cell-value').should('have.html', initialValue)); - } - } - }); - - it('copy multiple rows and columns from one table to another', () => { - DashTable.hideColumnById(0, '0'); - - DashTable.getCell(10, 0).click(); - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(13, 2).click(); - - DOM.focused.type(`${Key.Meta}c`); - cy.get(`#table2 tbody tr td.column-${0}`).eq(0).click(); - DOM.focused.type(`${Key.Meta}v`); - cy.get(`#table2 tbody tr td.column-${3}`).eq(2).click(); - - DashTable.getCell(16, 6).click(); - DOM.focused.type(Key.Shift, { release: false }); - - for (let row = 10; row <= 13; ++row) { - for (let column = 0; column <= 2; ++column) { - let initialValue: string; - - DashTable.getCell(row, column).within(() => cy.get('.dash-cell-value').then($cells => initialValue = $cells[0].innerHTML)); - cy.get(`#table2 tbody tr td.column-${column}`).eq(row - 10).within(() => cy.get('.dash-cell-value').should('have.html', initialValue)); - } - } - }); - }); - - // Commenting this test as Cypress team is having issues with the copy/paste scenario - // LINK: https://github.com/cypress-io/cypress/issues/2386 - describe('BE roundtrip on copy-paste', () => { - it('on cell modification', () => { - DashTable.getCell(0, 0).click(); - DOM.focused.type(`10${Key.Enter}`); - - cy.wait(1000); - - DashTable - .getCell(0, 0, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.html', '10')) - .then(() => { - DashTable.getCell(0, 1, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.html', 'MODIFIED')); - }); - }); - - it('with unsorted, unfiltered data', () => { - DashTable.getCell(0, 0).click(); - DOM.focused.type(`${Key.Meta}c`); - - DashTable.getCell(1, 0).click(); - DOM.focused.type(`${Key.Meta}v`); - - cy.wait(1000); - - DashTable - .getCell(1, 1, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.html', 'MODIFIED')); - DashTable - .getCell(1, 0, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.value', '0')); - - DashTable.getCell(1, 1).click(); - DOM.focused.type(`${Key.Meta}c`); - - DashTable.getCell(2, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - - cy.wait(1000); - - DashTable - .getCell(2, 1, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.value', 'MODIFIED')); - }); - - it('BE rountrip with sorted, unfiltered data', () => { - cy.get('#table tr th.column-2 .column-header--sort').last().click(); - - DashTable.getCell(0, 0).click(); - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.value', '11')); - - DOM.focused.type(`${Key.Meta}c`); - - DashTable.getCell(1, 0).click(); - DOM.focused.type(`${Key.Meta}v`); - - cy.wait(1000); - - DashTable - .getCell(1, 1, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.html', 'MODIFIED')); - DashTable - .getCell(1, 0, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.value', '11')); - - DashTable.getCell(1, 1).click(); - DOM.focused.type(`${Key.Meta}c`); - - DashTable.getCell(2, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - - cy.wait(1000); - - DashTable - .getCell(2, 1, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.value', 'MODIFIED')); - DashTable - .getCell(1, 0, State.Any) - .within(() => cy.get('.dash-cell-value').should('have.html', '11')); - }); - }); -}); - -describe('copy/paste behaviour with markdown', () => { - beforeEach(() => { - cy.visit('http://localhost:8087') - }); - - describe('single cell', () => { - it('copy markdown to non-markdown', () => { - DashTable.getCell(0, 3).click(); - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(0, 2).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 2).within(() => { - cy.get('.dash-cell-value').should('have.attr', 'value', '![Communication tactics](https://dash.plot.ly/assets/images/logo.png)'); - }); - }); - - it('copy markdown to markdown', () => { - DashTable.getCell(0, 1).click(); - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(0, 0).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 0).within(() => { - cy.get('.dash-cell-value > p > a').should('have.html', 'Debt collection'); - cy.get('.dash-cell-value > p > a').should('have.attr', 'href', 'plot.ly'); - }); - }); - - describe('copy non-markdown to markdown', () => { - it('null/empty value', () => { - DashTable.getCell(0, 2).click(); - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(0, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(0, 1).within(() => { - cy.get('.dash-cell-value > p').should('have.html', 'null'); - }); - }); - - it('regular value', () => { - DashTable.getCell(1, 2).click(); - DOM.focused.type(`${Key.Meta}c`); - DashTable.getCell(1, 1).click(); - DOM.focused.type(`${Key.Meta}v`); - DashTable.getCell(1, 1).within(() => { - cy.get('.dash-cell-value > p').should('have.html', 'Medical'); - }); - }); - }); - }); -}); diff --git a/tests/cypress/tests/server/dash_test.ts b/tests/cypress/tests/server/dash_test.ts index b0ba93557..cf1a37a41 100644 --- a/tests/cypress/tests/server/dash_test.ts +++ b/tests/cypress/tests/server/dash_test.ts @@ -7,15 +7,6 @@ describe('dash basic', () => { cy.visit('http://localhost:8081'); }); - it('can get cell', () => { - DashTable.getCell(0, 0).click(); - DashTable.getCell(0, 0).within(() => cy.get('input').should('have.value', '0')); - - cy.get('button.next-page').click(); - DashTable.getCell(0, 0).click(); - DashTable.getCell(0, 0).within(() => cy.get('input').should('have.value', '250')); - }); - it('cell click selects all text', () => { DashTable.getCell(0, 1).click(); DashTable.getCell(0, 1).within(() => diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py new file mode 100644 index 000000000..055624294 --- /dev/null +++ b/tests/selenium/conftest.py @@ -0,0 +1,162 @@ +import pytest +from dash.testing.browser import Browser + +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +READY = '.dash-spreadsheet:not(.dash-loading)' +LOADING = '.dash-spreadsheet.dash-loading' +ANY = '.dash-spreadsheet' + +class CopyPasteContext: + def __init__ (self, dt): + self.dt = dt + + def __enter__(self): + ActionChains(self.dt.driver).key_down(Keys.CONTROL).send_keys('c').key_up( + Keys.CONTROL + ).perform() + + def __exit__(self, type, value, traceback): + ActionChains(self.dt.driver).key_down(Keys.CONTROL).send_keys('v').key_up( + Keys.CONTROL + ).perform() + + +class DataTableContext: + def __init__ (self, target): + self.target = target + + def __enter__(self): + return self.target + + def __exit__(self, type, value, traceback): + return + + +class HoldKeyContext: + def __init__ (self, dt, key): + self.dt = dt + self.key = key + + def __enter__(self): + ActionChains(self.dt.driver).key_down(self.key).perform() + + def __exit__(self, type, value, traceback): + ActionChains(self.dt.driver).key_up(self.key).perform() + + +class DataTableCellFacade(object): + def __init__(self, id, dt): + self.id = id + self.dt = dt + + def get(self, row, col, state=READY): + return self.dt.find_elements( + '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) + )[row] if isinstance(col, int) else self.dt.find_elements( + '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) + )[row] + + def click(self, row, col, state=READY): + return self.get(row, col, state).click() + + def get_text(self, row, col, state=READY): + cell = self.get(row, col, state).find_element_by_css_selector( + '.dash-cell-value' + ) + + value = cell.get_attribute('value') + return ( + value + if value is not None and value != '' + else cell.get_attribute('innerHTML') + ) + + +class DataTableColumnFacade(object): + def __init__(self, id, dt): + self.id = id + self.dt = dt + + def get(self, row, col_id, state = READY): + return self.dt.find_elements( + '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) + )[row] + + def hide(self, row, col_id, state = READY): + self.get(row, col_id, state).find_element_by_css_selector( + '.column-header--hide' + ).click() + + def sort(self, row, col_id, state = READY): + self.get(row, col_id, state).find_element_by_css_selector( + '.column-header--sort' + ).click() + + +class DataTableFacade(object): + def __init__(self, id, dt): + self.id = id + self.dt = dt + + self.cell = DataTableCellFacade(id, dt) + self.column = DataTableColumnFacade(id, dt) + + def click_next_page(self): + self.dt.find_element( + '#{} button.next-page'.format(self.id) + ).click() + + def click_prev_page(self): + self.dt.find_element( + '#{} button.previous-page' + ).click() + + +class DashTableMixin(object): + def table(self, id): + return DataTableContext( + DataTableFacade(id, self) + ) + + def copy(self): + return CopyPasteContext(self) + + def hold(self, key): + return HoldKeyContext(self, key) + + +class DashTableComposite(Browser, DashTableMixin): + def __init__(self, server, **kwargs): + super(DashTableComposite, self).__init__(**kwargs) + self.server = server + + self.READY = READY + self.LOADING = LOADING + self.ANY = ANY + + def start_server(self, app, **kwargs): + """start the local server with app""" + + # start server with app and pass Dash arguments + self.server(app, **kwargs) + + # set the default server_url, it implicitly call wait_for_page + self.server_url = self.server.url + + +@pytest.fixture +def dt(request, dash_thread_server, tmpdir): + with DashTableComposite( + dash_thread_server, + browser=request.config.getoption("webdriver"), + remote=request.config.getoption("remote"), + remote_url=request.config.getoption("remote_url"), + headless=request.config.getoption("headless"), + options=request.config.hook.pytest_setup_options(), + download_path=tmpdir.mkdir("download").strpath, + percy_assets_root=request.config.getoption("percy_assets"), + percy_finalize=request.config.getoption("nopercyfinalize"), + ) as dc: + yield dc \ No newline at end of file diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py new file mode 100644 index 000000000..92b82bf0c --- /dev/null +++ b/tests/selenium/test_basic_copy_paste.py @@ -0,0 +1,200 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import pandas as pd + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url) +df = rawDf.to_dict('rows') + +def get_app(): + app = dash.Dash(__name__) + + app.layout = html.Div( + [ + DataTable( + id="table", + data=df[0:250], + columns=[{"name": i, "id": i, "hideable": i == "Complaint ID"} for i in rawDf.columns], + editable=True, + sort_action='native', + include_headers_on_copy_paste=True, + ), + DataTable( + id="table2", + data=df[0:10], + columns=[{"name": i, "id": i, "deletable": True} for i in rawDf.columns], + editable=True, + sort_action='native', + include_headers_on_copy_paste=True, + ), + ] + ) + + @app.callback( + Output("table", "data"), + [Input("table", "data_timestamp")], + [State("table", "data"), State("table", "data_previous")], + ) + # pylint: disable=unused-argument + def update_data(timestamp, current, previous): + # pylint: enable=unused-argument + if timestamp is None or current is None or previous is None: + raise PreventUpdate + + modified = False + if len(current) == len(previous): + for (i, datum) in enumerate(current): + previous_datum = previous[i] + + if datum['Unnamed: 0'] != previous_datum['Unnamed: 0']: + datum['Complaint ID'] = 'MODIFIED' + modified = True + + if modified: + return current + else: + raise PreventUpdate + + return app + + +def test_tbcp001_copy_paste_callback(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.cell.click(0, 0) + with dt.copy(): + target.cell.click(1, 0) + + assert target.cell.get_text(1, 0) == '0' + assert target.cell.get_text(1, 1) == 'MODIFIED' + + +def test_tbcp002_sorted_copy_paste_callback(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.column.sort(0, rawDf.columns[2]) + + assert target.cell.get_text(0,0) == '11' + + target.cell.click(0,0) + with dt.copy(): + target.cell.click(1,0) + + assert target.cell.get_text(1,0) == '11' + assert target.cell.get_text(1,1) == 'MODIFIED' + + target.cell.click(1,1) + with dt.copy(): + target.cell.click(2,1) + + assert target.cell.get_text(1,0) == '11' + assert target.cell.get_text(2,1) == 'MODIFIED' + + +def test_tbcp003_copy_multiple_rows(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + with dt.hold(Keys.SHIFT): + target.cell.click(0, 0) + target.cell.click(2, 0) + + with dt.copy(): + target.cell.click(3, 0) + + for i in range(3): + assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) + assert target.cell.get_text(i + 3, 1) == 'MODIFIED' + + +def test_tbcp004_copy_9_and_10(dt): + dt.start_server(get_app()) + + with dt.table('table') as source, dt.table('table2') as target: + source.cell.click(9, 0) + with dt.hold(Keys.SHIFT): + ActionChains(dt.driver).send_keys(Keys.DOWN).perform() + + with dt.copy(): + target.cell.click(0, 0) + + for row in range(2): + for col in range(1): + assert target.cell.get_text(row, col) == source.cell.get_text(row + 9, col) + + +def test_tbcp005_copy_multiple_rows_and_columns(dt): + dt.start_server(get_app()) + + with dt.table('table') as table: + table.cell.click(0, 1) + with dt.hold(Keys.SHIFT): + table.cell.click(2, 2) + + with dt.copy(): + table.cell.click(3, 1) + + for row in range(3): + for col in range(1, 3): + assert table.cell.get_text(row + 3, col) == table.cell.get_text(row, col) + + +def test_tbcp006_copy_paste_between_tables(dt): + dt.start_server(get_app()) + + with dt.table('table') as source, dt.table('table2') as target: + source.cell.click(10, 0) + with dt.hold(Keys.SHIFT): + source.cell.click(13, 3) + + with dt.copy(): + target.cell.click(0, 0) + + for row in range(4): + for col in range(4): + assert source.cell.get_text(row + 10, col) == target.cell.get_text(row, col) + + +def test_tbcp007_copy_paste_with_hidden_column(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.column.hide(0, 'Complaint ID') + target.cell.click(0, 0) + with dt.hold(Keys.SHIFT): + target.cell.click(2, 2) + + with dt.copy(): + target.cell.click(3, 1) + + for row in range(3): + for col in range(3): + assert target.cell.get_text(row, col) == target.cell.get_text(row + 3, col + 1) + + +def test_tbcp008_copy_paste_between_tables_with_hidden_columns(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.column.hide(0, 'Complaint ID') + target.cell.click(10, 0) + with dt.hold(Keys.SHIFT): + target.cell.click(13, 2) + + with dt.copy(): + target.cell.click(0, 0) + + for row in range(4): + for col in range(3): + assert target.cell.get_text(row + 10, col) == target.cell.get_text(row, col) \ No newline at end of file diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py new file mode 100644 index 000000000..aa824c350 --- /dev/null +++ b/tests/selenium/test_basic_operations.py @@ -0,0 +1,46 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import pandas as pd + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url) +df = rawDf.to_dict('rows') + +def get_app(): + app = dash.Dash(__name__) + + app.layout = DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=df, + editable=True, + filter_action="native", + fixed_columns={ 'headers': True, 'data': -1 }, + fixed_rows={ 'headers': True, 'data': -1 }, + page_action="native", + page_current=0, + page_size=250, + row_deletable=True, + row_selectable=True, + sort_action="native", + ) + + return app + + +def test_tbst001_get_cell(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + assert target.cell.get_text(0, rawDf.columns[0]) == '0' + target.click_next_page() + assert target.cell.get_text(0, rawDf.columns[0]) == '250' \ No newline at end of file diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py new file mode 100644 index 000000000..9d2ddd914 --- /dev/null +++ b/tests/selenium/test_markdown_copy_paste.py @@ -0,0 +1,87 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import pandas as pd + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url) +rawDf['Complaint ID'] = rawDf['Complaint ID'].map(lambda x: '**' + str(x) + '**') +rawDf['Product'] = rawDf['Product'].map(lambda x: '[' + str(x) + '](plot.ly)') +rawDf['Issue'] = rawDf['Issue'].map(lambda x: '![' + str(x) + '](https://dash.plot.ly/assets/images/logo.png)') +rawDf['State'] = rawDf['State'].map(lambda x: '```python\n"{}"\n```'.format(x)) + +df = rawDf.to_dict('rows') + +def get_app(): + app = dash.Dash(__name__) + + app.layout = DataTable( + id="table", + data=df[0:250], + columns=[ + {"id": "Complaint ID", "name": "Complaint ID", "presentation": "markdown"}, + {"id": "Product", "name": "Product", "presentation": "markdown"}, + {"id": "Sub-product", "name": "Sub-product"}, + {"id": "Issue", "name": "Issue", "presentation": "markdown"}, + {"id": "Sub-issue", "name": "Sub-issue"}, + {"id": "State", "name": "State", "presentation": "markdown"}, + {"id": "ZIP", "name": "ZIP"} + ], + editable=True, + sort_action='native', + include_headers_on_copy_paste=True + ) + + return app + + +def test_tmcp001_copy_markdown_to_text(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.cell.click(0, 'Issue') + with dt.copy(): + target.cell.click(0, 'Sub-product') + + assert target.cell.get_text(0, 2) == df[0].get('Issue') + + +def test_tmcp002_copy_markdown_to_markdown(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.cell.click(0, 'Product') + with dt.copy(): + target.cell.click(0, 'Complaint ID') + + assert target.cell.get_text(0, 'Complaint ID') == target.cell.get_text(0, 'Product') + + +def test_tmcp003_copy_text_to_markdown(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.cell.click(1, 'Sub-product') + with dt.copy(): + target.cell.click(1, 'Product') + + assert target.cell.get(1, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == df[1].get('Sub-product') + + +def test_tmcp004_copy_null_text_to_markdown(dt): + dt.start_server(get_app()) + + with dt.table('table') as target: + target.cell.click(0, 'Sub-product') + with dt.copy(): + target.cell.click(0, 'Product') + + assert target.cell.get(0, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == 'null' \ No newline at end of file From 68a2f3b4c84bca8f1fc420f93a28b1bb1117df85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 09:17:57 -0500 Subject: [PATCH 02/38] Dash w/ testing packages --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 61ea6d32c..b08bd1107 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -81,7 +81,7 @@ jobs: . venv/bin/activate pip install -r dev-requirements.txt --quiet git clone --depth 1 git@github.com:plotly/dash.git dash-main - pip install -e ./dash-main[dev] --quiet + pip install -e ./dash-main[dev,testing] --quiet cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. - run: From 944475685863147e693ac37dc0d45f133b167d9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 10:01:10 -0500 Subject: [PATCH 03/38] explicit wait --- tests/selenium/conftest.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 055624294..c268549d6 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -1,12 +1,16 @@ import pytest from dash.testing.browser import Browser -from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.wait import WebDriverWait READY = '.dash-spreadsheet:not(.dash-loading)' LOADING = '.dash-spreadsheet.dash-loading' ANY = '.dash-spreadsheet' +TIMEOUT = 10 class CopyPasteContext: def __init__ (self, dt): @@ -52,6 +56,8 @@ def __init__(self, id, dt): self.dt = dt def get(self, row, col, state=READY): + self.dt.wait_for_table(self.id, state) + return self.dt.find_elements( '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) )[row] if isinstance(col, int) else self.dt.find_elements( @@ -80,6 +86,8 @@ def __init__(self, id, dt): self.dt = dt def get(self, row, col_id, state = READY): + self.dt.wait_for_table(self.id, state) + return self.dt.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) )[row] @@ -104,17 +112,26 @@ def __init__(self, id, dt): self.column = DataTableColumnFacade(id, dt) def click_next_page(self): + self.dt.wait_for_table(self.id) + self.dt.find_element( '#{} button.next-page'.format(self.id) ).click() def click_prev_page(self): + self.dt.wait_for_table(self.id) + self.dt.find_element( '#{} button.previous-page' ).click() class DashTableMixin(object): + def wait_for_table(self, id, state=ANY): + return WebDriverWait(self.driver, TIMEOUT).until( + EC.presence_of_element_located((By.CSS_SELECTOR, '#{} {}'.format(id, state))) + ) + def table(self, id): return DataTableContext( DataTableFacade(id, self) From 188c87e574582ee746c3d6689dd93658b3773aa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 13:47:31 -0500 Subject: [PATCH 04/38] dt -> test --- tests/selenium/conftest.py | 52 +++++++------- tests/selenium/test_basic_copy_paste.py | 80 +++++++++++----------- tests/selenium/test_basic_operations.py | 6 +- tests/selenium/test_markdown_copy_paste.py | 32 ++++----- 4 files changed, 85 insertions(+), 85 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index c268549d6..cf2a3c9a2 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -13,16 +13,16 @@ TIMEOUT = 10 class CopyPasteContext: - def __init__ (self, dt): - self.dt = dt + def __init__ (self, test): + self.test = test def __enter__(self): - ActionChains(self.dt.driver).key_down(Keys.CONTROL).send_keys('c').key_up( + ActionChains(self.test.driver).key_down(Keys.CONTROL).send_keys('c').key_up( Keys.CONTROL ).perform() def __exit__(self, type, value, traceback): - ActionChains(self.dt.driver).key_down(Keys.CONTROL).send_keys('v').key_up( + ActionChains(self.test.driver).key_down(Keys.CONTROL).send_keys('v').key_up( Keys.CONTROL ).perform() @@ -39,28 +39,28 @@ def __exit__(self, type, value, traceback): class HoldKeyContext: - def __init__ (self, dt, key): - self.dt = dt + def __init__ (self, test, key): + self.test = test self.key = key def __enter__(self): - ActionChains(self.dt.driver).key_down(self.key).perform() + ActionChains(self.test.driver).key_down(self.key).perform() def __exit__(self, type, value, traceback): - ActionChains(self.dt.driver).key_up(self.key).perform() + ActionChains(self.test.driver).key_up(self.key).perform() class DataTableCellFacade(object): - def __init__(self, id, dt): + def __init__(self, id, test): self.id = id - self.dt = dt + self.test = test def get(self, row, col, state=READY): - self.dt.wait_for_table(self.id, state) + self.test.wait_for_table(self.id, state) - return self.dt.find_elements( + return self.test.find_elements( '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) - )[row] if isinstance(col, int) else self.dt.find_elements( + )[row] if isinstance(col, int) else self.test.find_elements( '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) )[row] @@ -81,14 +81,14 @@ def get_text(self, row, col, state=READY): class DataTableColumnFacade(object): - def __init__(self, id, dt): + def __init__(self, id, test): self.id = id - self.dt = dt + self.test = test def get(self, row, col_id, state = READY): - self.dt.wait_for_table(self.id, state) + self.test.wait_for_table(self.id, state) - return self.dt.find_elements( + return self.test.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) )[row] @@ -104,24 +104,24 @@ def sort(self, row, col_id, state = READY): class DataTableFacade(object): - def __init__(self, id, dt): + def __init__(self, id, test): self.id = id - self.dt = dt + self.test = test - self.cell = DataTableCellFacade(id, dt) - self.column = DataTableColumnFacade(id, dt) + self.cell = DataTableCellFacade(id, test) + self.column = DataTableColumnFacade(id, test) def click_next_page(self): - self.dt.wait_for_table(self.id) + self.test.wait_for_table(self.id) - self.dt.find_element( + self.test.find_element( '#{} button.next-page'.format(self.id) ).click() def click_prev_page(self): - self.dt.wait_for_table(self.id) + self.test.wait_for_table(self.id) - self.dt.find_element( + self.test.find_element( '#{} button.previous-page' ).click() @@ -164,7 +164,7 @@ def start_server(self, app, **kwargs): @pytest.fixture -def dt(request, dash_thread_server, tmpdir): +def test(request, dash_thread_server, tmpdir): with DashTableComposite( dash_thread_server, browser=request.config.getoption("webdriver"), diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index 92b82bf0c..d967149cc 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -67,50 +67,50 @@ def update_data(timestamp, current, previous): return app -def test_tbcp001_copy_paste_callback(dt): - dt.start_server(get_app()) +def test_tbcp001_copy_paste_callback(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.cell.click(0, 0) - with dt.copy(): + with test.copy(): target.cell.click(1, 0) assert target.cell.get_text(1, 0) == '0' assert target.cell.get_text(1, 1) == 'MODIFIED' -def test_tbcp002_sorted_copy_paste_callback(dt): - dt.start_server(get_app()) +def test_tbcp002_sorted_copy_paste_callback(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.column.sort(0, rawDf.columns[2]) assert target.cell.get_text(0,0) == '11' target.cell.click(0,0) - with dt.copy(): + with test.copy(): target.cell.click(1,0) assert target.cell.get_text(1,0) == '11' assert target.cell.get_text(1,1) == 'MODIFIED' target.cell.click(1,1) - with dt.copy(): + with test.copy(): target.cell.click(2,1) assert target.cell.get_text(1,0) == '11' assert target.cell.get_text(2,1) == 'MODIFIED' -def test_tbcp003_copy_multiple_rows(dt): - dt.start_server(get_app()) +def test_tbcp003_copy_multiple_rows(test): + test.start_server(get_app()) - with dt.table('table') as target: - with dt.hold(Keys.SHIFT): + with test.table('table') as target: + with test.hold(Keys.SHIFT): target.cell.click(0, 0) target.cell.click(2, 0) - with dt.copy(): + with test.copy(): target.cell.click(3, 0) for i in range(3): @@ -118,15 +118,15 @@ def test_tbcp003_copy_multiple_rows(dt): assert target.cell.get_text(i + 3, 1) == 'MODIFIED' -def test_tbcp004_copy_9_and_10(dt): - dt.start_server(get_app()) +def test_tbcp004_copy_9_and_10(test): + test.start_server(get_app()) - with dt.table('table') as source, dt.table('table2') as target: + with test.table('table') as source, test.table('table2') as target: source.cell.click(9, 0) - with dt.hold(Keys.SHIFT): - ActionChains(dt.driver).send_keys(Keys.DOWN).perform() + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).perform() - with dt.copy(): + with test.copy(): target.cell.click(0, 0) for row in range(2): @@ -134,15 +134,15 @@ def test_tbcp004_copy_9_and_10(dt): assert target.cell.get_text(row, col) == source.cell.get_text(row + 9, col) -def test_tbcp005_copy_multiple_rows_and_columns(dt): - dt.start_server(get_app()) +def test_tbcp005_copy_multiple_rows_and_columns(test): + test.start_server(get_app()) - with dt.table('table') as table: + with test.table('table') as table: table.cell.click(0, 1) - with dt.hold(Keys.SHIFT): + with test.hold(Keys.SHIFT): table.cell.click(2, 2) - with dt.copy(): + with test.copy(): table.cell.click(3, 1) for row in range(3): @@ -150,15 +150,15 @@ def test_tbcp005_copy_multiple_rows_and_columns(dt): assert table.cell.get_text(row + 3, col) == table.cell.get_text(row, col) -def test_tbcp006_copy_paste_between_tables(dt): - dt.start_server(get_app()) +def test_tbcp006_copy_paste_between_tables(test): + test.start_server(get_app()) - with dt.table('table') as source, dt.table('table2') as target: + with test.table('table') as source, test.table('table2') as target: source.cell.click(10, 0) - with dt.hold(Keys.SHIFT): + with test.hold(Keys.SHIFT): source.cell.click(13, 3) - with dt.copy(): + with test.copy(): target.cell.click(0, 0) for row in range(4): @@ -166,16 +166,16 @@ def test_tbcp006_copy_paste_between_tables(dt): assert source.cell.get_text(row + 10, col) == target.cell.get_text(row, col) -def test_tbcp007_copy_paste_with_hidden_column(dt): - dt.start_server(get_app()) +def test_tbcp007_copy_paste_with_hidden_column(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.column.hide(0, 'Complaint ID') target.cell.click(0, 0) - with dt.hold(Keys.SHIFT): + with test.hold(Keys.SHIFT): target.cell.click(2, 2) - with dt.copy(): + with test.copy(): target.cell.click(3, 1) for row in range(3): @@ -183,16 +183,16 @@ def test_tbcp007_copy_paste_with_hidden_column(dt): assert target.cell.get_text(row, col) == target.cell.get_text(row + 3, col + 1) -def test_tbcp008_copy_paste_between_tables_with_hidden_columns(dt): - dt.start_server(get_app()) +def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.column.hide(0, 'Complaint ID') target.cell.click(10, 0) - with dt.hold(Keys.SHIFT): + with test.hold(Keys.SHIFT): target.cell.click(13, 2) - with dt.copy(): + with test.copy(): target.cell.click(0, 0) for row in range(4): diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index aa824c350..f655697c4 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -37,10 +37,10 @@ def get_app(): return app -def test_tbst001_get_cell(dt): - dt.start_server(get_app()) +def test_tbst001_get_cell(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: assert target.cell.get_text(0, rawDf.columns[0]) == '0' target.click_next_page() assert target.cell.get_text(0, rawDf.columns[0]) == '250' \ No newline at end of file diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index 9d2ddd914..97de1f360 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -43,45 +43,45 @@ def get_app(): return app -def test_tmcp001_copy_markdown_to_text(dt): - dt.start_server(get_app()) +def test_tmcp001_copy_markdown_to_text(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.cell.click(0, 'Issue') - with dt.copy(): + with test.copy(): target.cell.click(0, 'Sub-product') assert target.cell.get_text(0, 2) == df[0].get('Issue') -def test_tmcp002_copy_markdown_to_markdown(dt): - dt.start_server(get_app()) +def test_tmcp002_copy_markdown_to_markdown(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.cell.click(0, 'Product') - with dt.copy(): + with test.copy(): target.cell.click(0, 'Complaint ID') assert target.cell.get_text(0, 'Complaint ID') == target.cell.get_text(0, 'Product') -def test_tmcp003_copy_text_to_markdown(dt): - dt.start_server(get_app()) +def test_tmcp003_copy_text_to_markdown(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.cell.click(1, 'Sub-product') - with dt.copy(): + with test.copy(): target.cell.click(1, 'Product') assert target.cell.get(1, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == df[1].get('Sub-product') -def test_tmcp004_copy_null_text_to_markdown(dt): - dt.start_server(get_app()) +def test_tmcp004_copy_null_text_to_markdown(test): + test.start_server(get_app()) - with dt.table('table') as target: + with test.table('table') as target: target.cell.click(0, 'Sub-product') - with dt.copy(): + with test.copy(): target.cell.click(0, 'Product') assert target.cell.get(0, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == 'null' \ No newline at end of file From 91a882a2c015d3b40b33136d08fb573a4d3a7e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 13:52:46 -0500 Subject: [PATCH 05/38] no context for copy/paste --- tests/selenium/conftest.py | 23 ++++------- tests/selenium/test_basic_copy_paste.py | 48 ++++++++++++++-------- tests/selenium/test_markdown_copy_paste.py | 24 +++++++---- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index cf2a3c9a2..6e6d092b1 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -12,20 +12,6 @@ ANY = '.dash-spreadsheet' TIMEOUT = 10 -class CopyPasteContext: - def __init__ (self, test): - self.test = test - - def __enter__(self): - ActionChains(self.test.driver).key_down(Keys.CONTROL).send_keys('c').key_up( - Keys.CONTROL - ).perform() - - def __exit__(self, type, value, traceback): - ActionChains(self.test.driver).key_down(Keys.CONTROL).send_keys('v').key_up( - Keys.CONTROL - ).perform() - class DataTableContext: def __init__ (self, target): @@ -138,7 +124,14 @@ def table(self, id): ) def copy(self): - return CopyPasteContext(self) + ActionChains(self.driver).key_down(Keys.CONTROL).send_keys('c').key_up( + Keys.CONTROL + ).perform() + + def paste(self): + ActionChains(self.driver).key_down(Keys.CONTROL).send_keys('v').key_up( + Keys.CONTROL + ).perform() def hold(self, key): return HoldKeyContext(self, key) diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index d967149cc..8b72403bb 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -72,8 +72,10 @@ def test_tbcp001_copy_paste_callback(test): with test.table('table') as target: target.cell.click(0, 0) - with test.copy(): - target.cell.click(1, 0) + + test.copy() + target.cell.click(1, 0) + test.paste() assert target.cell.get_text(1, 0) == '0' assert target.cell.get_text(1, 1) == 'MODIFIED' @@ -88,15 +90,19 @@ def test_tbcp002_sorted_copy_paste_callback(test): assert target.cell.get_text(0,0) == '11' target.cell.click(0,0) - with test.copy(): - target.cell.click(1,0) + + test.copy() + target.cell.click(1,0) + test.paste() assert target.cell.get_text(1,0) == '11' assert target.cell.get_text(1,1) == 'MODIFIED' target.cell.click(1,1) - with test.copy(): - target.cell.click(2,1) + + test.copy() + target.cell.click(2,1) + test.paste() assert target.cell.get_text(1,0) == '11' assert target.cell.get_text(2,1) == 'MODIFIED' @@ -110,8 +116,9 @@ def test_tbcp003_copy_multiple_rows(test): target.cell.click(0, 0) target.cell.click(2, 0) - with test.copy(): - target.cell.click(3, 0) + test.copy() + target.cell.click(3, 0) + test.paste() for i in range(3): assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) @@ -126,8 +133,9 @@ def test_tbcp004_copy_9_and_10(test): with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).perform() - with test.copy(): - target.cell.click(0, 0) + test.copy() + target.cell.click(0, 0) + test.paste() for row in range(2): for col in range(1): @@ -142,8 +150,9 @@ def test_tbcp005_copy_multiple_rows_and_columns(test): with test.hold(Keys.SHIFT): table.cell.click(2, 2) - with test.copy(): - table.cell.click(3, 1) + test.copy() + table.cell.click(3, 1) + test.paste() for row in range(3): for col in range(1, 3): @@ -158,8 +167,9 @@ def test_tbcp006_copy_paste_between_tables(test): with test.hold(Keys.SHIFT): source.cell.click(13, 3) - with test.copy(): - target.cell.click(0, 0) + test.copy() + target.cell.click(0, 0) + test.paste() for row in range(4): for col in range(4): @@ -175,8 +185,9 @@ def test_tbcp007_copy_paste_with_hidden_column(test): with test.hold(Keys.SHIFT): target.cell.click(2, 2) - with test.copy(): - target.cell.click(3, 1) + test.copy() + target.cell.click(3, 1) + test.paste() for row in range(3): for col in range(3): @@ -192,8 +203,9 @@ def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): with test.hold(Keys.SHIFT): target.cell.click(13, 2) - with test.copy(): - target.cell.click(0, 0) + test.copy() + target.cell.click(0, 0) + test.paste() for row in range(4): for col in range(3): diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index 97de1f360..f62c98ad5 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -48,8 +48,10 @@ def test_tmcp001_copy_markdown_to_text(test): with test.table('table') as target: target.cell.click(0, 'Issue') - with test.copy(): - target.cell.click(0, 'Sub-product') + + test.copy() + target.cell.click(0, 'Sub-product') + test.paste() assert target.cell.get_text(0, 2) == df[0].get('Issue') @@ -59,8 +61,10 @@ def test_tmcp002_copy_markdown_to_markdown(test): with test.table('table') as target: target.cell.click(0, 'Product') - with test.copy(): - target.cell.click(0, 'Complaint ID') + + test.copy() + target.cell.click(0, 'Complaint ID') + test.paste() assert target.cell.get_text(0, 'Complaint ID') == target.cell.get_text(0, 'Product') @@ -70,8 +74,10 @@ def test_tmcp003_copy_text_to_markdown(test): with test.table('table') as target: target.cell.click(1, 'Sub-product') - with test.copy(): - target.cell.click(1, 'Product') + + test.copy() + target.cell.click(1, 'Product') + test.paste() assert target.cell.get(1, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == df[1].get('Sub-product') @@ -81,7 +87,9 @@ def test_tmcp004_copy_null_text_to_markdown(test): with test.table('table') as target: target.cell.click(0, 'Sub-product') - with test.copy(): - target.cell.click(0, 'Product') + + test.copy() + target.cell.click(0, 'Product') + test.paste() assert target.cell.get(0, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == 'null' \ No newline at end of file From 0e707da73c2355bc6649194b7e642243e41bdce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:21:03 -0500 Subject: [PATCH 06/38] add preconditions --- dev-requirements.txt | 1 + tests/selenium/conftest.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index 260b56bf8..4cbe1f54e 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ flake8 pandas +preconditions xlrd \ No newline at end of file diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 6e6d092b1..489b559a7 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -1,19 +1,29 @@ import pytest -from dash.testing.browser import Browser +from dash.testing.browser import Browser +from preconditions import preconditions from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait +validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or (isinstance(col, int) and col >= 0) +validate_col_id = lambda col_id: isinstance(col_id, str) and len(col_id) > 0 +validate_id = lambda id: isinstance(id, str) and len(id) > 0 +validate_key = lambda key: isinstance(key, str) and len(key) == 1 +validate_row = lambda row: isinstance(row, int) and row >= 0 +validate_state = lambda state: state in [READY, LOADING, ANY] +validate_target = lambda target: isinstance(target, DataTableFacade) +validate_test = lambda test: isinstance(test, DashTableMixin) + READY = '.dash-spreadsheet:not(.dash-loading)' LOADING = '.dash-spreadsheet.dash-loading' ANY = '.dash-spreadsheet' TIMEOUT = 10 - class DataTableContext: + @preconditions(validate_target) def __init__ (self, target): self.target = target @@ -25,6 +35,7 @@ def __exit__(self, type, value, traceback): class HoldKeyContext: + @preconditions(validate_test, validate_key) def __init__ (self, test, key): self.test = test self.key = key @@ -37,10 +48,12 @@ def __exit__(self, type, value, traceback): class DataTableCellFacade(object): + @preconditions(validate_id, validate_test) def __init__(self, id, test): self.id = id self.test = test + @preconditions(validate_row, validate_col, validate_state) def get(self, row, col, state=READY): self.test.wait_for_table(self.id, state) @@ -50,9 +63,11 @@ def get(self, row, col, state=READY): '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) )[row] + @preconditions(validate_row, validate_col, validate_state) def click(self, row, col, state=READY): return self.get(row, col, state).click() + @preconditions(validate_row, validate_col, validate_state) def get_text(self, row, col, state=READY): cell = self.get(row, col, state).find_element_by_css_selector( '.dash-cell-value' @@ -67,10 +82,12 @@ def get_text(self, row, col, state=READY): class DataTableColumnFacade(object): + @preconditions(validate_id, validate_test) def __init__(self, id, test): self.id = id self.test = test + @preconditions(validate_row, validate_col_id, validate_state) def get(self, row, col_id, state = READY): self.test.wait_for_table(self.id, state) @@ -78,11 +95,13 @@ def get(self, row, col_id, state = READY): '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) )[row] + @preconditions(validate_row, validate_col_id, validate_state) def hide(self, row, col_id, state = READY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--hide' ).click() + @preconditions(validate_row, validate_col_id, validate_state) def sort(self, row, col_id, state = READY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--sort' @@ -90,6 +109,7 @@ def sort(self, row, col_id, state = READY): class DataTableFacade(object): + @preconditions(validate_id, validate_test) def __init__(self, id, test): self.id = id self.test = test @@ -113,11 +133,13 @@ def click_prev_page(self): class DashTableMixin(object): + @preconditions(validate_id, validate_state) def wait_for_table(self, id, state=ANY): return WebDriverWait(self.driver, TIMEOUT).until( EC.presence_of_element_located((By.CSS_SELECTOR, '#{} {}'.format(id, state))) ) + @preconditions(validate_id) def table(self, id): return DataTableContext( DataTableFacade(id, self) @@ -133,6 +155,8 @@ def paste(self): Keys.CONTROL ).perform() + + @preconditions(validate_key) def hold(self, key): return HoldKeyContext(self, key) From 7c7e8bdd49fe759ee635fc9ad50789d24e2414b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:26:30 -0500 Subject: [PATCH 07/38] 3.6.9 -> 3.6? --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b08bd1107..97899f5d0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -52,7 +52,7 @@ jobs: "server-test": docker: - - image: circleci/python:3.6.9-node-browsers + - image: circleci/python:3.6-node-browsers - image: cypress/base:10 steps: From 2b7d33b7e7b594ed38683bd6b878a077ddaf2fa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:33:08 -0500 Subject: [PATCH 08/38] test -> mixin --- tests/selenium/conftest.py | 52 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 489b559a7..ea07f8e24 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -15,7 +15,7 @@ validate_row = lambda row: isinstance(row, int) and row >= 0 validate_state = lambda state: state in [READY, LOADING, ANY] validate_target = lambda target: isinstance(target, DataTableFacade) -validate_test = lambda test: isinstance(test, DashTableMixin) +validate_mixin = lambda mixin: isinstance(mixin, DashTableMixin) READY = '.dash-spreadsheet:not(.dash-loading)' LOADING = '.dash-spreadsheet.dash-loading' @@ -35,31 +35,31 @@ def __exit__(self, type, value, traceback): class HoldKeyContext: - @preconditions(validate_test, validate_key) - def __init__ (self, test, key): - self.test = test + @preconditions(validate_mixin, validate_key) + def __init__ (self, mixin, key): + self.mixin = mixin self.key = key def __enter__(self): - ActionChains(self.test.driver).key_down(self.key).perform() + ActionChains(self.mixin.driver).key_down(self.key).perform() def __exit__(self, type, value, traceback): - ActionChains(self.test.driver).key_up(self.key).perform() + ActionChains(self.mixin.driver).key_up(self.key).perform() class DataTableCellFacade(object): - @preconditions(validate_id, validate_test) - def __init__(self, id, test): + @preconditions(validate_id, validate_mixin) + def __init__(self, id, mixin): self.id = id - self.test = test + self.mixin = mixin @preconditions(validate_row, validate_col, validate_state) def get(self, row, col, state=READY): - self.test.wait_for_table(self.id, state) + self.mixin.wait_for_table(self.id, state) - return self.test.find_elements( + return self.mixin.find_elements( '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) - )[row] if isinstance(col, int) else self.test.find_elements( + )[row] if isinstance(col, int) else self.mixin.find_elements( '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) )[row] @@ -82,16 +82,16 @@ def get_text(self, row, col, state=READY): class DataTableColumnFacade(object): - @preconditions(validate_id, validate_test) - def __init__(self, id, test): + @preconditions(validate_id, validate_mixin) + def __init__(self, id, mixin): self.id = id - self.test = test + self.mixin = mixin @preconditions(validate_row, validate_col_id, validate_state) def get(self, row, col_id, state = READY): - self.test.wait_for_table(self.id, state) + self.mixin.wait_for_table(self.id, state) - return self.test.find_elements( + return self.mixin.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) )[row] @@ -109,25 +109,25 @@ def sort(self, row, col_id, state = READY): class DataTableFacade(object): - @preconditions(validate_id, validate_test) - def __init__(self, id, test): + @preconditions(validate_id, validate_mixin) + def __init__(self, id, mixin): self.id = id - self.test = test + self.mixin = mixin - self.cell = DataTableCellFacade(id, test) - self.column = DataTableColumnFacade(id, test) + self.cell = DataTableCellFacade(id, mixin) + self.column = DataTableColumnFacade(id, mixin) def click_next_page(self): - self.test.wait_for_table(self.id) + self.mixin.wait_for_table(self.id) - self.test.find_element( + self.mixin.find_element( '#{} button.next-page'.format(self.id) ).click() def click_prev_page(self): - self.test.wait_for_table(self.id) + self.mixin.wait_for_table(self.id) - self.test.find_element( + self.mixin.find_element( '#{} button.previous-page' ).click() From a4dea44d55a79178ddca1949a939f4791dcc103b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:35:19 -0500 Subject: [PATCH 09/38] consistent DashTable / DataTable usage --- tests/selenium/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index ea07f8e24..d7d06838a 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -15,7 +15,7 @@ validate_row = lambda row: isinstance(row, int) and row >= 0 validate_state = lambda state: state in [READY, LOADING, ANY] validate_target = lambda target: isinstance(target, DataTableFacade) -validate_mixin = lambda mixin: isinstance(mixin, DashTableMixin) +validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) READY = '.dash-spreadsheet:not(.dash-loading)' LOADING = '.dash-spreadsheet.dash-loading' @@ -132,7 +132,7 @@ def click_prev_page(self): ).click() -class DashTableMixin(object): +class DataTableMixin(object): @preconditions(validate_id, validate_state) def wait_for_table(self, id, state=ANY): return WebDriverWait(self.driver, TIMEOUT).until( @@ -161,9 +161,9 @@ def hold(self, key): return HoldKeyContext(self, key) -class DashTableComposite(Browser, DashTableMixin): +class DataTableComposite(Browser, DataTableMixin): def __init__(self, server, **kwargs): - super(DashTableComposite, self).__init__(**kwargs) + super(DataTableComposite, self).__init__(**kwargs) self.server = server self.READY = READY @@ -182,7 +182,7 @@ def start_server(self, app, **kwargs): @pytest.fixture def test(request, dash_thread_server, tmpdir): - with DashTableComposite( + with DataTableComposite( dash_thread_server, browser=request.config.getoption("webdriver"), remote=request.config.getoption("remote"), From d3dc8f3f3b85e2c57f00a83cb908cc4ab678ad86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:40:51 -0500 Subject: [PATCH 10/38] leading _ on private variables --- tests/selenium/conftest.py | 76 +++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index d7d06838a..ee70cf9c0 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -8,22 +8,22 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or (isinstance(col, int) and col >= 0) -validate_col_id = lambda col_id: isinstance(col_id, str) and len(col_id) > 0 -validate_id = lambda id: isinstance(id, str) and len(id) > 0 -validate_key = lambda key: isinstance(key, str) and len(key) == 1 -validate_row = lambda row: isinstance(row, int) and row >= 0 -validate_state = lambda state: state in [READY, LOADING, ANY] -validate_target = lambda target: isinstance(target, DataTableFacade) -validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) - -READY = '.dash-spreadsheet:not(.dash-loading)' -LOADING = '.dash-spreadsheet.dash-loading' -ANY = '.dash-spreadsheet' -TIMEOUT = 10 +_validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or (isinstance(col, int) and col >= 0) +_validate_col_id = lambda col_id: isinstance(col_id, str) and len(col_id) > 0 +_validate_id = lambda id: isinstance(id, str) and len(id) > 0 +_validate_key = lambda key: isinstance(key, str) and len(key) == 1 +_validate_row = lambda row: isinstance(row, int) and row >= 0 +_validate_state = lambda state: state in [_READY, _LOADING, _ANY] +_validate_target = lambda target: isinstance(target, DataTableFacade) +_validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) + +_READY = '.dash-spreadsheet:not(.dash-loading)' +_LOADING = '.dash-spreadsheet.dash-loading' +_ANY = '.dash-spreadsheet' +_TIMEOUT = 10 class DataTableContext: - @preconditions(validate_target) + @preconditions(_validate_target) def __init__ (self, target): self.target = target @@ -35,7 +35,7 @@ def __exit__(self, type, value, traceback): class HoldKeyContext: - @preconditions(validate_mixin, validate_key) + @preconditions(_validate_mixin, _validate_key) def __init__ (self, mixin, key): self.mixin = mixin self.key = key @@ -48,13 +48,13 @@ def __exit__(self, type, value, traceback): class DataTableCellFacade(object): - @preconditions(validate_id, validate_mixin) + @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): self.id = id self.mixin = mixin - @preconditions(validate_row, validate_col, validate_state) - def get(self, row, col, state=READY): + @preconditions(_validate_row, _validate_col, _validate_state) + def get(self, row, col, state = _READY): self.mixin.wait_for_table(self.id, state) return self.mixin.find_elements( @@ -63,12 +63,12 @@ def get(self, row, col, state=READY): '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) )[row] - @preconditions(validate_row, validate_col, validate_state) - def click(self, row, col, state=READY): + @preconditions(_validate_row, _validate_col, _validate_state) + def click(self, row, col, state = _READY): return self.get(row, col, state).click() - @preconditions(validate_row, validate_col, validate_state) - def get_text(self, row, col, state=READY): + @preconditions(_validate_row, _validate_col, _validate_state) + def get_text(self, row, col, state = _READY): cell = self.get(row, col, state).find_element_by_css_selector( '.dash-cell-value' ) @@ -82,34 +82,34 @@ def get_text(self, row, col, state=READY): class DataTableColumnFacade(object): - @preconditions(validate_id, validate_mixin) + @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): self.id = id self.mixin = mixin - @preconditions(validate_row, validate_col_id, validate_state) - def get(self, row, col_id, state = READY): + @preconditions(_validate_row, _validate_col_id, _validate_state) + def get(self, row, col_id, state = _READY): self.mixin.wait_for_table(self.id, state) return self.mixin.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) )[row] - @preconditions(validate_row, validate_col_id, validate_state) - def hide(self, row, col_id, state = READY): + @preconditions(_validate_row, _validate_col_id, _validate_state) + def hide(self, row, col_id, state = _READY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--hide' ).click() - @preconditions(validate_row, validate_col_id, validate_state) - def sort(self, row, col_id, state = READY): + @preconditions(_validate_row, _validate_col_id, _validate_state) + def sort(self, row, col_id, state = _READY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--sort' ).click() class DataTableFacade(object): - @preconditions(validate_id, validate_mixin) + @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): self.id = id self.mixin = mixin @@ -133,13 +133,13 @@ def click_prev_page(self): class DataTableMixin(object): - @preconditions(validate_id, validate_state) - def wait_for_table(self, id, state=ANY): - return WebDriverWait(self.driver, TIMEOUT).until( + @preconditions(_validate_id, _validate_state) + def wait_for_table(self, id, state = _ANY): + return WebDriverWait(self.driver, _TIMEOUT).until( EC.presence_of_element_located((By.CSS_SELECTOR, '#{} {}'.format(id, state))) ) - @preconditions(validate_id) + @preconditions(_validate_id) def table(self, id): return DataTableContext( DataTableFacade(id, self) @@ -156,7 +156,7 @@ def paste(self): ).perform() - @preconditions(validate_key) + @preconditions(_validate_key) def hold(self, key): return HoldKeyContext(self, key) @@ -166,9 +166,9 @@ def __init__(self, server, **kwargs): super(DataTableComposite, self).__init__(**kwargs) self.server = server - self.READY = READY - self.LOADING = LOADING - self.ANY = ANY + self.READY = _READY + self.LOADING = _LOADING + self.ANY = _ANY def start_server(self, app, **kwargs): """start the local server with app""" From 52c1ac836657f9772e1cd8fb18202f574d746b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 14:58:01 -0500 Subject: [PATCH 11/38] prefix private method with _ --- tests/selenium/conftest.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index ee70cf9c0..a84a41ee2 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -55,7 +55,7 @@ def __init__(self, id, mixin): @preconditions(_validate_row, _validate_col, _validate_state) def get(self, row, col, state = _READY): - self.mixin.wait_for_table(self.id, state) + self.mixin._wait_for_table(self.id, state) return self.mixin.find_elements( '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) @@ -89,7 +89,7 @@ def __init__(self, id, mixin): @preconditions(_validate_row, _validate_col_id, _validate_state) def get(self, row, col_id, state = _READY): - self.mixin.wait_for_table(self.id, state) + self.mixin._wait_for_table(self.id, state) return self.mixin.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) @@ -118,14 +118,14 @@ def __init__(self, id, mixin): self.column = DataTableColumnFacade(id, mixin) def click_next_page(self): - self.mixin.wait_for_table(self.id) + self.mixin._wait_for_table(self.id) self.mixin.find_element( '#{} button.next-page'.format(self.id) ).click() def click_prev_page(self): - self.mixin.wait_for_table(self.id) + self.mixin._wait_for_table(self.id) self.mixin.find_element( '#{} button.previous-page' @@ -134,7 +134,7 @@ def click_prev_page(self): class DataTableMixin(object): @preconditions(_validate_id, _validate_state) - def wait_for_table(self, id, state = _ANY): + def _wait_for_table(self, id, state = _ANY): return WebDriverWait(self.driver, _TIMEOUT).until( EC.presence_of_element_located((By.CSS_SELECTOR, '#{} {}'.format(id, state))) ) From b8344753b2bd68a6e624ced5de2170c5727e9bfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 15:10:16 -0500 Subject: [PATCH 12/38] empty lines --- tests/selenium/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index a84a41ee2..a984d73fa 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -155,7 +155,6 @@ def paste(self): Keys.CONTROL ).perform() - @preconditions(_validate_key) def hold(self, key): return HoldKeyContext(self, key) From 37d40f4bf545737431ec2751edf70837fe365ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 15:49:15 -0500 Subject: [PATCH 13/38] disable percy run for test suite --- .circleci/config.yml | 2 +- tests/selenium/conftest.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 97899f5d0..af7eb3335 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,7 +80,7 @@ jobs: python -m venv venv || virtualenv venv . venv/bin/activate pip install -r dev-requirements.txt --quiet - git clone --depth 1 git@github.com:plotly/dash.git dash-main + git clone --depth 1 -b dt-582-selenium-tests git@github.com:plotly/dash.git dash-main pip install -e ./dash-main[dev,testing] --quiet cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index a984d73fa..48e7814f6 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -191,5 +191,6 @@ def test(request, dash_thread_server, tmpdir): download_path=tmpdir.mkdir("download").strpath, percy_assets_root=request.config.getoption("percy_assets"), percy_finalize=request.config.getoption("nopercyfinalize"), + percy_run=False, ) as dc: yield dc \ No newline at end of file From a402a11c6c4efceaa6fd18df5ab5abef1989e025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Tue, 18 Feb 2020 15:52:40 -0500 Subject: [PATCH 14/38] -new --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index af7eb3335..e82b5986a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -80,7 +80,7 @@ jobs: python -m venv venv || virtualenv venv . venv/bin/activate pip install -r dev-requirements.txt --quiet - git clone --depth 1 -b dt-582-selenium-tests git@github.com:plotly/dash.git dash-main + git clone --depth 1 -b dt-582-new-selenium-tests git@github.com:plotly/dash.git dash-main pip install -e ./dash-main[dev,testing] --quiet cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. From bdc256306bdeb0232b03cc863ee5e52727862d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Wed, 19 Feb 2020 10:42:24 -0500 Subject: [PATCH 15/38] rewrite 3 basic tests --- tests/cypress/tests/server/dash_test.ts | 41 ------------------------- tests/selenium/conftest.py | 12 +++++++- tests/selenium/test_basic_operations.py | 33 +++++++++++++++++++- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/tests/cypress/tests/server/dash_test.ts b/tests/cypress/tests/server/dash_test.ts index cf1a37a41..aed5e8a11 100644 --- a/tests/cypress/tests/server/dash_test.ts +++ b/tests/cypress/tests/server/dash_test.ts @@ -7,47 +7,6 @@ describe('dash basic', () => { cy.visit('http://localhost:8081'); }); - it('cell click selects all text', () => { - DashTable.getCell(0, 1).click(); - DashTable.getCell(0, 1).within(() => - cy.get('input').then($inputs => { - const $input = $inputs[0]; - - expect($input.selectionStart).to.equal(0); - expect($input.selectionEnd).to.equal($input.value.length); - }) - ); - }); - - // https://github.com/plotly/dash-table/issues/50 - it('can edit last and update data on "enter"', () => { - DashTable.focusCell(249, 0); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc${Key.Enter}`); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - }); - - // https://github.com/plotly/dash-table/issues/107 - it('can edit last and update data on "tab"', () => { - DashTable.focusCell(249, 0); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc`); - - cy.tab(); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - }); describe('ArrowKeys navigation', () => { describe('When active, but not focused', () => { // https://github.com/plotly/dash-table/issues/141 diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 48e7814f6..d422a2c5c 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -12,10 +12,11 @@ _validate_col_id = lambda col_id: isinstance(col_id, str) and len(col_id) > 0 _validate_id = lambda id: isinstance(id, str) and len(id) > 0 _validate_key = lambda key: isinstance(key, str) and len(key) == 1 +_validate_keys = lambda keys: isinstance(keys, str) and len(keys) > 0 +_validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) _validate_row = lambda row: isinstance(row, int) and row >= 0 _validate_state = lambda state: state in [_READY, _LOADING, _ANY] _validate_target = lambda target: isinstance(target, DataTableFacade) -_validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) _READY = '.dash-spreadsheet:not(.dash-loading)' _LOADING = '.dash-spreadsheet.dash-loading' @@ -159,6 +160,15 @@ def paste(self): def hold(self, key): return HoldKeyContext(self, key) + def get_selected_text(self): + return self.driver.execute_script( + 'return window.getSelection().toString()' + ) + + @preconditions(_validate_keys) + def send_keys(self, keys): + self.driver.switch_to.active_element.send_keys(keys) + class DataTableComposite(Browser, DataTableMixin): def __init__(self, server, **kwargs): diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index f655697c4..8eae021e8 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -43,4 +43,35 @@ def test_tbst001_get_cell(test): with test.table('table') as target: assert target.cell.get_text(0, rawDf.columns[0]) == '0' target.click_next_page() - assert target.cell.get_text(0, rawDf.columns[0]) == '250' \ No newline at end of file + assert target.cell.get_text(0, rawDf.columns[0]) == '250' + + +def test_tbst002_select_all_text(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 1) + + assert target.cell.get_text(0, 1) == test.get_selected_text() + + +# https://github.com/plotly/dash-table/issues/50 +def test_tbst003_edit_on_enter(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc' + Keys.ENTER) + + assert target.cell.get_text(249, 0) == 'abc' + + +# https://github.com/plotly/dash-table/issues/107 +def test_tbst004_edit_on_tab(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc' + Keys.TAB) + + assert target.cell.get_text(249, 0) == 'abc' \ No newline at end of file From f0fb42141a80b143bc7b6f2f566777b996593904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 10:38:42 -0500 Subject: [PATCH 16/38] selenium support for tests with dblclick in table --- tests/cypress/tests/server/dash_test.ts | 100 ------------------------ tests/selenium/conftest.py | 48 +++++++++--- tests/selenium/test_basic_operations.py | 70 ++++++++++++++++- 3 files changed, 104 insertions(+), 114 deletions(-) delete mode 100644 tests/cypress/tests/server/dash_test.ts diff --git a/tests/cypress/tests/server/dash_test.ts b/tests/cypress/tests/server/dash_test.ts deleted file mode 100644 index aed5e8a11..000000000 --- a/tests/cypress/tests/server/dash_test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import DashTable from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; - -describe('dash basic', () => { - beforeEach(() => { - cy.visit('http://localhost:8081'); - }); - - describe('ArrowKeys navigation', () => { - describe('When active, but not focused', () => { - // https://github.com/plotly/dash-table/issues/141 - it('can edit last, update data on "arrowleft", and move one cell to the left', () => { - const startingCell = [249, 1]; - const targetCell = [249, 0]; - DashTable.focusCell(startingCell[0], startingCell[1]); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc${Key.ArrowLeft}`); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][1] = ${initialValue} -> abc`); - }); - }); - DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused'); - }); - - // https://github.com/plotly/dash-table/issues/141 - it('can edit last, update data on "arrowup", and move one cell up', () => { - const startingCell = [249, 0]; - const targetCell = [248, 0]; - DashTable.focusCell(startingCell[0], startingCell[1]); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc${Key.ArrowUp}`); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused'); - }); - - // https://github.com/plotly/dash-table/issues/141 - it('can edit last, update data on "arrowright", and move one cell to the right', () => { - const startingCell = [249, 0]; - const targetCell = [249, 1]; - DashTable.focusCell(startingCell[0], startingCell[1]); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc${Key.ArrowRight}`); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused'); - }); - - // https://github.com/plotly/dash-table/issues/141 - it('can edit last, update data on "arrowdown", and move one cell down', () => { - const startingCell = [249, 0]; - const targetCell = [249, 1]; - DashTable.focusCell(startingCell[0], startingCell[1]); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc${Key.ArrowRight}`); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - DashTable.getCell(targetCell[0], targetCell[1]).should('have.class', 'focused'); - }); - }); - }); - - it('can edit last and update data when clicking outside of cell', () => { - DashTable.focusCell(249, 0); - DOM.focused.then($input => { - const initialValue = $input.val(); - - DOM.focused.type(`abc`); - DashTable.getCell(248, 0).click(); - - cy.get('#container').should($container => { - expect($container.first()[0].innerText).to.equal(`[249][0] = ${initialValue} -> abc`); - }); - }); - }); - - it('can get cell with double click', () => { - DashTable.getCell(3, 1).within(() => cy.get('div').dblclick()); - DashTable.getCell(3, 1).should('have.class', 'focused'); - }); -}); diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index d422a2c5c..88f7cc8ba 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -55,32 +55,54 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_col, _validate_state) - def get(self, row, col, state = _READY): - self.mixin._wait_for_table(self.id, state) - - return self.mixin.find_elements( - '#{} {} tbody tr td.dash-cell.column-{}'.format(self.id, state, col) - )[row] if isinstance(col, int) else self.mixin.find_elements( - '#{} {} tbody tr td.dash-cell[data-dash-column="{}"]'.format(self.id, state, col) - )[row] + def _get_cell_value(self, row, col, selector, state = _READY): + return self.get(row, col, state).find_element_by_css_selector('.dash-cell-value') @preconditions(_validate_row, _validate_col, _validate_state) def click(self, row, col, state = _READY): return self.get(row, col, state).click() @preconditions(_validate_row, _validate_col, _validate_state) - def get_text(self, row, col, state = _READY): - cell = self.get(row, col, state).find_element_by_css_selector( - '.dash-cell-value' + def double_click(self, row, col, state = _READY): + ac = ActionChains(self.mixin.driver) + ac.move_to_element(self._get_cell_value(row, col, state)) + ac.pause(1) + ac.double_click() + return ac.perform() + + @preconditions(_validate_row, _validate_col, _validate_state) + def get(self, row, col, state = _READY): + self.mixin._wait_for_table(self.id, state) + + return self.mixin.find_element( + '#{} {} tbody td.dash-cell.column-{}[data-dash-row="{}"]'.format(self.id, state, col, row) + ) if isinstance(col, int) else self.mixin.find_element( + '#{} {} tbody td.dash-cell[data-dash-column="{}"][data-dash-row="{}"]'.format(self.id, state, col, row) ) - value = cell.get_attribute('value') + @preconditions(_validate_row, _validate_col, _validate_state) + def get_text(self, row, col, state = _READY): + el = self._get_cell_value(row, col, state) + + value = el.get_attribute('value') return ( value if value is not None and value != '' - else cell.get_attribute('innerHTML') + else el.get_attribute('innerHTML') ) + @preconditions(_validate_row, _validate_col, _validate_state) + def is_active(self, row, col, state = _READY): + input = self.get(row, col, state).find_element_by_css_selector('input') + + return 'focused' in input.get_attribute('class').split(' ') + + @preconditions(_validate_row, _validate_col, _validate_state) + def is_focused(self, row, col, state = _READY): + cell = self.get(row, col, state) + + return 'focused' in cell.get_attribute('class').split(' ') + class DataTableColumnFacade(object): @preconditions(_validate_id, _validate_mixin) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 8eae021e8..df056cccf 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -74,4 +74,72 @@ def test_tbst004_edit_on_tab(test): target.cell.click(249, 0) test.send_keys('abc' + Keys.TAB) - assert target.cell.get_text(249, 0) == 'abc' \ No newline at end of file + assert target.cell.get_text(249, 0) == 'abc' + + +def test_tbst005_edit_last_row_on_click_outside(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc') + target.cell.click(248, 0) + + assert target.cell.get_text(249, 0) == 'abc' + + +# https://github.com/plotly/dash-table/issues/141 +def test_tbst006_focused_arrow_left(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 1) + test.send_keys('abc' + Keys.LEFT) + + assert target.cell.get_text(249, 1) == 'abc' + assert target.cell.is_focused(249, 0) + + +# https://github.com/plotly/dash-table/issues/141 +def test_tbst007_active_focused_arrow_right(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc' + Keys.RIGHT) + + assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.is_focused(249, 1) + + +# https://github.com/plotly/dash-table/issues/141 +def test_tbst008_active_focused_arrow_up(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc' + Keys.UP) + + assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.is_focused(248, 0) + + +# https://github.com/plotly/dash-table/issues/141 +def test_tbst009_active_focused_arrow_down(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(249, 0) + test.send_keys('abc' + Keys.DOWN) + + assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.is_focused(249, 0) + + +def test_tbst010_active_with_dblclick(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.double_click(0, 0) + assert target.cell.is_active(0, 0) + assert target.cell.get_text(0, 0) == test.get_selected_text() \ No newline at end of file From 4001b67efbcf9634ed8a1a1786b8d4ca6abddc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 11:04:55 -0500 Subject: [PATCH 17/38] cypress -> selenium column sort / row select / row delete tests --- package.json | 2 - tests/cypress/dash/v_be_page.py | 105 ------------------ tests/cypress/tests/server/delete_row_test.ts | 18 --- .../cypress/tests/server/select_props_test.ts | 22 ---- tests/selenium/conftest.py | 26 +++++ tests/selenium/test_basic_operations.py | 69 +++++++++++- 6 files changed, 92 insertions(+), 150 deletions(-) delete mode 100644 tests/cypress/dash/v_be_page.py delete mode 100644 tests/cypress/tests/server/delete_row_test.ts diff --git a/package.json b/package.json index bd95c2598..f6ebc2d6c 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "private::build:js-test-standalone": "run-s \"private::build -- --mode development --config webpack.test.standalone.config.js\"", "private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"", "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", - "private::host_dash8081": "python tests/cypress/dash/v_be_page.py", "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", "private::host_dash8084": "python tests/cypress/dash/v_data_loading.py", "private::host_dash8085": "python tests/cypress/dash/v_default.py", @@ -30,7 +29,6 @@ "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", - "private::wait_dash8081": "wait-on http://localhost:8081", "private::wait_dash8083": "wait-on http://localhost:8083", "private::wait_dash8084": "wait-on http://localhost:8084", "private::wait_dash8085": "wait-on http://localhost:8085", diff --git a/tests/cypress/dash/v_be_page.py b/tests/cypress/dash/v_be_page.py deleted file mode 100644 index 20775f7f6..000000000 --- a/tests/cypress/dash/v_be_page.py +++ /dev/null @@ -1,105 +0,0 @@ -# pylint: disable=global-statement -import dash -from dash.dependencies import Input, Output -import dash_html_components as html -import os -import pandas as pd -import sys - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(module) for module in module_names] -dash_table = modules[0] - -url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" -df = pd.read_csv(url) - -app = dash.Dash() -app.css.config.serve_locally = True -app.scripts.config.serve_locally = True - -app.layout = html.Div( - [ - html.Div(id="container", children="Hello World"), - dash_table.DataTable( - id="table", - data=[], - page_action="custom", - page_current=0, - page_size=250, - columns=[ - {"id": 0, "name": "Complaint ID"}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - fixed_columns={ 'headers': True, 'data': -1 }, - fixed_rows={ 'headers': True, 'data': -1 }, - row_selectable=True, - row_deletable=True, - sort_action="custom", - filter_action='none', - editable=True, - ), - ] -) - - -@app.callback(Output("table", "data"), [ - Input("table", "page_current"), - Input("table", "page_size"), - Input("table", "sort_by") -]) -def updateData(current_page, page_size, sort_by): - start_index = current_page * page_size - end_index = start_index + page_size - print(str(start_index) + "," + str(end_index)) - print(sort_by) - - if (sort_by is None or len(sort_by) == 0): - sorted_df = df.values - else: - sorted_df = df.sort_index( - axis=sort_by[0]['column_id'], - ascending=(sort_by[0]['direction'] == 'asc') - ).values - - return sorted_df[start_index:end_index] - - -@app.callback( - Output("container", "children"), - [Input("table", "data"), Input("table", "data_previous")], -) -def findModifiedValue(data, previous): - modification = "None" - - if data is None or previous is None: - return modification - - for (y, row) in enumerate(data): - row_prev = previous[y] - - for (x, col) in enumerate(row): - if col != row_prev[x]: - modification = "[{}][{}] = {} -> {}".format(y, x, row_prev[x], col) - - return modification - - -if __name__ == "__main__": - app.run_server(port=8081, debug=False) diff --git a/tests/cypress/tests/server/delete_row_test.ts b/tests/cypress/tests/server/delete_row_test.ts deleted file mode 100644 index 488420c4e..000000000 --- a/tests/cypress/tests/server/delete_row_test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import DashTable from 'cypress/DashTable'; - -describe('delete', () => { - beforeEach(() => cy.visit('http://localhost:8081')); - - it('can delete row', () => { - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '0')); - DashTable.getDelete(0).click(); - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - }); - - it('can delete row when sorted', () => { - cy.get('tr th.column-0 .column-header--sort').last().click({ force: true }).click({ force: true }); - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '28155')); - DashTable.getDelete(0).click(); - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '28154')); - }); -}); \ No newline at end of file diff --git a/tests/cypress/tests/server/select_props_test.ts b/tests/cypress/tests/server/select_props_test.ts index 43e124ba8..f42c6acec 100644 --- a/tests/cypress/tests/server/select_props_test.ts +++ b/tests/cypress/tests/server/select_props_test.ts @@ -74,28 +74,6 @@ function range(from: number, to: number, step?: number) { } describe('select row', () => { - describe('be pagination & sort', () => { - beforeEach(() => cy.visit('http://localhost:8081')); - - it('can select row', () => { - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(0).within(() => cy.get('input').should('be.checked')); - }); - - it('can select row when sorted', () => { - cy.get('tr th.column-0 .column-header--sort').last().click({ force: true }); - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(0).within(() => cy.get('input').should('be.checked')); - }); - - it('select, sort, new row is not selected', () => { - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(0).within(() => cy.get('input').should('be.checked')); - cy.get('tr th.column-0 .column-header--sort').last().click({ force: true }); - DashTable.getSelect(0).within(() => cy.get('input').should('not.be.checked')); - }); - }); - describe('fe pagination & sort', () => { beforeEach(() => cy.visit('http://localhost:8083')); diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 88f7cc8ba..ea5d09bbc 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -131,6 +131,31 @@ def sort(self, row, col_id, state = _READY): ).click() +class DataTableRowFacade(object): + @preconditions(_validate_id, _validate_mixin) + def __init__(self, id, mixin): + self.id = id + self.mixin = mixin + + @preconditions(_validate_row, _validate_state) + def delete(self, row, state = _READY): + return self.mixin.find_elements( + '#{} {} tbody tr td.dash-delete-cell'.format(self.id, state) + )[row].click() + + @preconditions(_validate_row, _validate_state) + def select(self, row, state = _READY): + return self.mixin.find_elements( + '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) + )[row].click() + + @preconditions(_validate_row, _validate_state) + def is_selected(self, row, state = _READY): + return self.mixin.find_elements( + '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) + )[row].find_element_by_css_selector('input').is_selected() + + class DataTableFacade(object): @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): @@ -139,6 +164,7 @@ def __init__(self, id, mixin): self.cell = DataTableCellFacade(id, mixin) self.column = DataTableColumnFacade(id, mixin) + self.row = DataTableRowFacade(id, mixin) def click_next_page(self): self.mixin._wait_for_table(self.id) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index df056cccf..1a9e21599 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -41,9 +41,9 @@ def test_tbst001_get_cell(test): test.start_server(get_app()) with test.table('table') as target: - assert target.cell.get_text(0, rawDf.columns[0]) == '0' + assert target.cell.get_text(0, 0) == '0' target.click_next_page() - assert target.cell.get_text(0, rawDf.columns[0]) == '250' + assert target.cell.get_text(0, 0) == '250' def test_tbst002_select_all_text(test): @@ -142,4 +142,67 @@ def test_tbst010_active_with_dblclick(test): with test.table('table') as target: target.cell.double_click(0, 0) assert target.cell.is_active(0, 0) - assert target.cell.get_text(0, 0) == test.get_selected_text() \ No newline at end of file + assert target.cell.get_text(0, 0) == test.get_selected_text() + + +def test_tbst011_delete_row(test): + test.start_server(get_app()) + + with test.table('table') as target: + text00 = target.cell.get_text(0, 0) + text01 = target.cell.get_text(1, 0) + target.row.delete(0) + + assert target.cell.get_text(0, 0) == text01 + + +def test_tbst012_delete_sorted_row(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + text00 = target.cell.get_text(0, 0) + text01 = target.cell.get_text(1, 0) + target.row.delete(0) + + assert target.cell.get_text(0, 0) == text01 + + +def test_tbst013_select_row(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.row.select(0) + + assert target.row.is_selected(0) + + +def test_tbst014_selected_sorted_row(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.row.select(0) + + assert target.row.is_selected(0) + + +def test_tbst015_selected_row_respects_sort(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.row.select(0) + + assert target.row.is_selected(0) + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + assert not target.row.is_selected(0) + + target.column.sort(0, rawDf.columns[0]) # DESC -> None + + assert target.row.is_selected(0) \ No newline at end of file From 9169ab8d5e43f6801ba74a47bfc5a75da24e9628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 12:51:20 -0500 Subject: [PATCH 18/38] cypress -> selenium, cell delete tests --- tests/cypress/dash/v_default.py | 82 ------------ .../cypress/tests/server/delete_cells_test.ts | 57 --------- tests/selenium/test_basic_operations.py | 121 +++++++++++++++++- 3 files changed, 119 insertions(+), 141 deletions(-) delete mode 100644 tests/cypress/dash/v_default.py delete mode 100644 tests/cypress/tests/server/delete_cells_test.ts diff --git a/tests/cypress/dash/v_default.py b/tests/cypress/dash/v_default.py deleted file mode 100644 index 994f92c56..000000000 --- a/tests/cypress/dash/v_default.py +++ /dev/null @@ -1,82 +0,0 @@ -# pylint: disable=global-statement -import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate -import dash_html_components as html -import os -import pandas as pd -import sys - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(x) for x in module_names] -dash_table = modules[0] - -url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" -df = pd.read_csv(url) -df = df.values - -app = dash.Dash() -app.css.config.serve_locally = True -app.scripts.config.serve_locally = True - -app.layout = html.Div( - [ - html.Div(id="container", children="Hello World"), - dash_table.DataTable( - id="table", - data=df[0:250], - columns=[ - {"id": 0, "name": "Complaint ID", "hideable": True}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True, - - ), - dash_table.DataTable( - id="table2", - data=df[0:10], - columns=[ - {"id": 0, "name": "Complaint ID", "hideable": True}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True, - ), - ] -) - - -if __name__ == "__main__": - app.run_server(port=8085, debug=False) diff --git a/tests/cypress/tests/server/delete_cells_test.ts b/tests/cypress/tests/server/delete_cells_test.ts deleted file mode 100644 index 3d0cac7ad..000000000 --- a/tests/cypress/tests/server/delete_cells_test.ts +++ /dev/null @@ -1,57 +0,0 @@ -import DashTable from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; - -describe('delete cells', () => { - beforeEach(() => { - cy.visit('http://localhost:8085'); - }); - - describe('unsorted data', () => { - it('can delete single cell', () => { - DashTable.getCell(0, 1).click(); - DashTable.getCell(0, 1).within(() => cy.get('.dash-cell-value').should('not.have.value', '')); - DOM.focused.type(`${Key.Backspace}${Key.ArrowDown}`); - DashTable.getCell(0, 1).within(() => cy.get('.dash-cell-value').should('have.html', '')); - }); - - it('can delete multiple cells', () => { - DashTable.getCell(0, 1).click(); - DOM.focused.type(`${Key.Shift}${Key.ArrowDown}${Key.ArrowRight}`); - DOM.focused.type(`${Key.Backspace}`); - DashTable.getCell(0, 0).click(); - - for (let row = 0; row <= 1; ++row) { - for (let column = 1; column <= 2; ++column) { - DashTable.getCell(row, column).within(() => cy.get('.dash-cell-value').should('have.html', '')); - } - } - }); - }); - - describe('sorted data', () => { - beforeEach(() => { - cy.get('tr th.column-0 .column-header--sort').last().click(); - }); - - it('can delete single cell', () => { - DashTable.getCell(0, 1).click(); - DashTable.getCell(0, 1).within(() => cy.get('.dash-cell-value').should('not.have.value', '')); - DOM.focused.type(`${Key.Backspace}${Key.ArrowDown}`); - DashTable.getCell(0, 1).within(() => cy.get('.dash-cell-value').should('have.html', '')); - }); - - it('can delete multiple cells', () => { - DashTable.getCell(0, 1).click(); - DOM.focused.type(`${Key.Shift}${Key.ArrowDown}${Key.ArrowRight}`); - DOM.focused.type(`${Key.Backspace}`); - DashTable.getCell(0, 0).click(); - - for (let row = 0; row <= 1; ++row) { - for (let column = 1; column <= 2; ++column) { - DashTable.getCell(row, column).within(() => cy.get('.dash-cell-value').should('have.html', '')); - } - } - }); - }); -}); diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 1a9e21599..042a85b35 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -10,7 +10,7 @@ from selenium.webdriver.common.action_chains import ActionChains import pandas as pd - +import time url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) df = rawDf.to_dict('rows') @@ -205,4 +205,121 @@ def test_tbst015_selected_row_respects_sort(test): target.column.sort(0, rawDf.columns[0]) # DESC -> None - assert target.row.is_selected(0) \ No newline at end of file + assert target.row.is_selected(0) + + +def test_tbst016_delete_cell(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) + test.send_keys(Keys.ENTER) + + assert target.cell.get_text(0, 1) == '' + + +# # https://github.com/plotly/dash-table/issues/700 +# def test_tbst017_delete_cell_updates_while_selected(test): +# test.start_server(get_app()) + +# with test.table('table') as target: +# target.cell.click(0, 1) +# test.send_keys(Keys.BACKSPACE) + +# assert target.cell.get_text(0, 1) == '' + + +def test_tbst018_delete_multiple_cells(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() + + for row in range(2): + for col in range(1, 3): + assert target.cell.get_text(row, col) == '' + + +# # https://github.com/plotly/dash-table/issues/700 +# def test_tbst019_delete_multiple_cells_while_selected(test): +# test.start_server(get_app()) + +# with test.table('table') as target: +# target.cell.click(0, 1) +# with test.hold(Keys.SHIFT): +# ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + +# ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + +# for row in range(2): +# for col in range(1, 3): +# assert target.cell.get_text(row, col) == '' + + +def test_tbst020_sorted_table_delete_cell(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) + test.send_keys(Keys.ENTER) + + assert target.cell.get_text(0, 1) == '' + + +# # https://github.com/plotly/dash-table/issues/700 +# def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): +# test.start_server(get_app()) + +# with test.table('table') as target: +# target.column.sort(0, rawDf.columns[0]) # None -> ASC +# target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + +# target.cell.click(0, 1) +# test.send_keys(Keys.BACKSPACE) + +# assert target.cell.get_text(0, 1) == '' + + +def test_tbst022_sorted_table_delete_multiple_cells(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() + + for row in range(2): + for col in range(1, 3): + assert target.cell.get_text(row, col) == '' + +# # https://github.com/plotly/dash-table/issues/700 +# def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): +# test.start_server(get_app()) + +# with test.table('table') as target: +# target.column.sort(0, rawDf.columns[0]) # None -> ASC +# target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + +# target.cell.click(0, 1) +# with test.hold(Keys.SHIFT): +# ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + +# ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + +# for row in range(2): +# for col in range(1, 3): +# assert target.cell.get_text(row, col) == '' \ No newline at end of file From 58a8f5c6dd27925dd65b9066fa69ee284437fabf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 13:03:28 -0500 Subject: [PATCH 19/38] remove 8085 --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index f6ebc2d6c..e81a56fbe 100644 --- a/package.json +++ b/package.json @@ -24,14 +24,12 @@ "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", "private::host_dash8084": "python tests/cypress/dash/v_data_loading.py", - "private::host_dash8085": "python tests/cypress/dash/v_default.py", "private::host_dash8086": "python tests/cypress/dash/v_pagination.py", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", "private::wait_dash8083": "wait-on http://localhost:8083", "private::wait_dash8084": "wait-on http://localhost:8084", - "private::wait_dash8085": "wait-on http://localhost:8085", "private::wait_dash8086": "wait-on http://localhost:8086", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", From cd26069bb45c2be5ea00dc54edbcc8b1b39ea381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 16:13:04 -0500 Subject: [PATCH 20/38] cypress -> selenium, pagination tests --- package.json | 2 - tests/cypress/dash/v_pagination.py | 64 ----- tests/cypress/tests/server/pagination_test.ts | 254 ------------------ tests/selenium/conftest.py | 75 +++++- tests/selenium/test_basic_operations.py | 8 +- tests/selenium/test_pagination.py | 200 ++++++++++++++ 6 files changed, 275 insertions(+), 328 deletions(-) delete mode 100644 tests/cypress/dash/v_pagination.py delete mode 100644 tests/cypress/tests/server/pagination_test.ts create mode 100644 tests/selenium/test_pagination.py diff --git a/package.json b/package.json index e81a56fbe..996e5f9d1 100644 --- a/package.json +++ b/package.json @@ -24,13 +24,11 @@ "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", "private::host_dash8084": "python tests/cypress/dash/v_data_loading.py", - "private::host_dash8086": "python tests/cypress/dash/v_pagination.py", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", "private::wait_dash8083": "wait-on http://localhost:8083", "private::wait_dash8084": "wait-on http://localhost:8084", - "private::wait_dash8086": "wait-on http://localhost:8086", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", diff --git a/tests/cypress/dash/v_pagination.py b/tests/cypress/dash/v_pagination.py deleted file mode 100644 index 7a8bc89e6..000000000 --- a/tests/cypress/dash/v_pagination.py +++ /dev/null @@ -1,64 +0,0 @@ -import dash -from dash.dependencies import Input, Output -from dash.exceptions import PreventUpdate -import dash_table -import dash_core_components as dcc -import dash_html_components as html -import pandas as pd - - -df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/gapminder2007.csv') - -df[' index'] = range(1, len(df) + 1) - -app = dash.Dash(__name__) - -PAGE_SIZE = 5 - -app.layout = html.Div([ - dcc.Location(id='url'), - dash_table.DataTable( - id='table', - columns=[ - {'name': i, 'id': i} for i in sorted(df.columns) - ], - page_current=0, - page_size=PAGE_SIZE, - page_count=None - ) -]) - - -@app.callback( - [Output('table', 'data'), - Output('table', 'page_action'), - Output('table', 'page_count')], - [Input('table', 'page_current'), - Input('table', 'page_size'), - Input('url', 'search')] -) -def update_table(page_current, page_size, pagination_mode): - - if not pagination_mode: - raise PreventUpdate - - mode = { - param.split('=')[0]: param.split('=')[1] - for param in pagination_mode.strip('?').split('&') - } - - data = None - - if mode['page_action'] == 'native': - data = df.to_dict('records') - else: - data = df.iloc[ - page_current * page_size: (page_current + 1) * page_size - ].to_dict('records') - - return data, mode.get('page_action', 'native'), \ - int(mode.get('page_count')) if mode.get('page_count') else None - - -if __name__ == '__main__': - app.run_server(port=8086, debug=True) diff --git a/tests/cypress/tests/server/pagination_test.ts b/tests/cypress/tests/server/pagination_test.ts deleted file mode 100644 index e3e691722..000000000 --- a/tests/cypress/tests/server/pagination_test.ts +++ /dev/null @@ -1,254 +0,0 @@ -import DashTable from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; - -const pagination_modes = ['native', 'custom'] - -describe('table pagination', () => { - - pagination_modes.forEach(mode => { - - describe(`can change pages with ${mode} page_action`, () => { - - before(() => { - cy.visit(`http://localhost:8086?page_action=${mode}&page_count=29`); - - // initial state: first page, previous/first buttons disabled - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - cy.get('button.first-page').should('be.disabled'); - cy.get('button.previous-page').should('be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '1'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - }); - - describe('with the navigation buttons', () => { - - it('with the next-page navigation button', () => { - - // go forward by five pages - for (let i = 0; i < 5; i++) { - cy.get('button.next-page').click(); - } - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '6'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '26')); - - }); - - it('with the previous-page navigation button', () => { - // go back by three pages - for (let i = 0; i < 3; i++) { - cy.get('button.previous-page').click(); - } - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '3'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '11')); - - }); - - it('with the first page button', () => { - cy.get('button.first-page').click(); - - cy.get('button.first-page').should('be.disabled'); - cy.get('button.previous-page').should('be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '1'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - - }); - - it('with the last page button', () => { - cy.get('button.last-page').click(); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '29'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('be.disabled'); - cy.get('button.last-page').should('be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '141')); - - }); - }); - - describe('with the text input box', () => { - describe('correctly navigates to the desired page', () => { - it('with unfocus', () => { - cy.get('input.current-page').click(); - DOM.focused.type(`14`); - cy.get('input.current-page').blur(); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '14'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '66')); - }), - it('with enter', () => { - cy.get('input.current-page').click(); - DOM.focused.type(`18${Key.Enter}`); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '18'); - cy.get('input.current-page').should('not.be.focused'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '86')); - }) - }); - - describe('can handle invalid page numbers', () => { - it('zero value', () => { - cy.get('input.current-page').click(); - DOM.focused.type('0'); - cy.get('input.current-page').blur(); - - cy.get('button.first-page').should('be.disabled'); - cy.get('button.previous-page').should('be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '1'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - - }); - - it('value higher than last page', () => { - cy.get('input.current-page').click(); - DOM.focused.type('100'); - cy.get('input.current-page').blur(); - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '29'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('be.disabled'); - cy.get('button.last-page').should('be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '141')); - }); - - it('negative value', () => { - - cy.get('input.current-page').click(); - DOM.focused.type('-1'); - cy.get('input.current-page').blur(); - - cy.get('button.first-page').should('be.disabled'); - cy.get('button.previous-page').should('be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '1'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - - }); - - it('non-numerical value', () => { - cy.get('input.current-page').click(); - DOM.focused.type('10'); - cy.get('input.current-page').blur(); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '10'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '46')); - - cy.get('input.current-page').click(); - DOM.focused.type('hello'); - cy.get('input.current-page').blur(); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '10'); - cy.get('.last-page').should('have.html', '29'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('not.be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '46')); - - }); - }); - }); - }); - }); - - describe('handles other page_count values', () => { - - describe('hides pagination', () => { - it('on single page', () => { - cy.visit(`http://localhost:8086?page_action=custom&page_count=1`); - - cy.get('.previous-next-container').should('not.exist'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - - }); - - it('on negative/zero values', () => { - cy.visit(`http://localhost:8086?page_action=custom&page_count=-1`); - - cy.get('.previous-next-container').should('not.exist'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - }); - }); - - it('undefined/none', () => { - - cy.visit(`http://localhost:8086?page_action=custom`); - - cy.get('.page-number').children().should('have.length', 1); - cy.get('.current-page').should('exist'); - cy.get('button.next-page').should('not.be.disabled'); - cy.get('button.last-page').should('be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '1')); - - }); - - it('limits pages', () => { - cy.visit(`http://localhost:8086?page_action=custom&page_count=10`); - cy.get('button.last-page').click(); - - cy.get('button.first-page').should('not.be.disabled'); - cy.get('button.previous-page').should('not.be.disabled'); - cy.get('input.current-page').should('have.attr', 'placeholder', '10'); - cy.get('.last-page').should('have.html', '10'); - cy.get('button.next-page').should('be.disabled'); - cy.get('button.last-page').should('be.disabled'); - - DashTable.getCell(0, 0).within(() => cy.get('.dash-cell-value').should('have.html', '46')); - }); - }); -}); diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index ea5d09bbc..b1460c89c 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -169,17 +169,86 @@ def __init__(self, id, mixin): def click_next_page(self): self.mixin._wait_for_table(self.id) - self.mixin.find_element( + return self.mixin.find_element( '#{} button.next-page'.format(self.id) ).click() def click_prev_page(self): self.mixin._wait_for_table(self.id) - self.mixin.find_element( - '#{} button.previous-page' + return self.mixin.find_element( + '#{} button.previous-page'.format(self.id) + ).click() + + def click_first_page(self): + self.mixin._wait_for_table(self.id) + + return self.mixin.find_element( + '#{} button.first-page'.format(self.id) + ).click() + + def click_last_page(self): + self.mixin._wait_for_table(self.id) + + return self.mixin.find_element( + '#{} button.last-page'.format(self.id) ).click() + def has_pagination(self): + self.mixin._wait_for_table(self.id) + + return len(self.mixin.find_elements('.previous-next-container')) != 0 + + def has_next_page(self): + self.mixin._wait_for_table(self.id) + + el = self.mixin.find_element( + '#{} button.next-page'.format(self.id) + ) + + return el is not None and el.is_enabled() + + def has_prev_page(self): + self.mixin._wait_for_table(self.id) + + el = self.mixin.find_element( + '#{} button.previous-page'.format(self.id) + ) + + return el is not None and el.is_enabled() + + def has_first_page(self): + self.mixin._wait_for_table(self.id) + + el = self.mixin.find_element( + '#{} button.first-page'.format(self.id) + ) + + return el is not None and el.is_enabled() + + def has_last_page(self): + self.mixin._wait_for_table(self.id) + + el = self.mixin.find_element( + '#{} button.last-page'.format(self.id) + ) + + return el is not None and el.is_enabled() + + def click_current_page(self): + self.mixin._wait_for_table(self.id) + + return self.mixin.find_element( + '#{} input.current-page'.format(self.id) + ).click() + + def get_current_page(self): + self.mixin._wait_for_table(self.id) + + return self.mixin.find_element( + '#{} input.current-page'.format(self.id) + ).get_attribute('placeholder') + class DataTableMixin(object): @preconditions(_validate_id, _validate_state) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 042a85b35..fa6a8f810 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -10,7 +10,7 @@ from selenium.webdriver.common.action_chains import ActionChains import pandas as pd -import time + url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) df = rawDf.to_dict('rows') @@ -24,11 +24,9 @@ def get_app(): data=df, editable=True, filter_action="native", - fixed_columns={ 'headers': True, 'data': -1 }, - fixed_rows={ 'headers': True, 'data': -1 }, + fixed_columns={ 'headers': True }, + fixed_rows={ 'headers': True }, page_action="native", - page_current=0, - page_size=250, row_deletable=True, row_selectable=True, sort_action="native", diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py new file mode 100644 index 000000000..36c859489 --- /dev/null +++ b/tests/selenium/test_pagination.py @@ -0,0 +1,200 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +import pytest +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import math +import pandas as pd + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url) +df = rawDf.to_dict('rows') + +PAGE_SIZE = 5 +pages = math.ceil(len(df) / PAGE_SIZE) + +def get_app(mode, data=df, page_count=None): + app = dash.Dash(__name__) + + if page_count is None: + page_count = math.ceil(len(data) / PAGE_SIZE) + + app.layout = DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=data if mode == 'native' else data[0 : PAGE_SIZE], + editable=True, + fixed_columns={ 'headers': True }, + fixed_rows={ 'headers': True }, + page_action=mode, + page_count=page_count, + page_size=PAGE_SIZE, + row_deletable=True, + row_selectable=True, + ) + + if mode == 'custom': + @app.callback( + [Output('table', 'data')], + [Input('table', 'page_current'), Input('table', 'page_size')] + ) + def update_table(page_current, page_size): + if page_current is None or page_size is None: + raise PreventUpdate + + return data[ + page_current * page_size: (page_current + 1) * page_size + ], + + return app + + +@pytest.mark.parametrize('mode', ['custom', 'native']) +def test_tpag001_next_previous(test, mode): + test.start_server(get_app(mode)) + + with test.table('table') as target: + assert target.cell.get_text(0, 0) == '0' + assert target.has_next_page() + assert not target.has_prev_page() + + target.click_next_page() + + assert target.cell.get_text(0, 0) == '5' + assert target.has_next_page() + assert target.has_prev_page() + + target.click_prev_page() + + assert target.cell.get_text(0, 0) == '0' + assert target.has_next_page() + assert not target.has_prev_page() + + +@pytest.mark.parametrize('mode', ['custom', 'native']) +def test_tpag002_ops_on_first_page(test, mode): + test.start_server(get_app(mode)) + + with test.table('table') as target: + assert target.get_current_page() == '1' + assert not target.has_first_page() + assert not target.has_prev_page() + assert target.has_next_page() + assert target.has_last_page() + + +@pytest.mark.parametrize('mode', ['custom', 'native']) +def test_tpag003_ops_on_last_page(test, mode): + test.start_server(get_app(mode)) + + with test.table('table') as target: + target.click_last_page() + + assert target.get_current_page() == str(pages) + assert target.has_first_page() + assert target.has_prev_page() + assert not target.has_next_page() + assert not target.has_last_page() + + +def test_tpag004_ops_input_with_enter(test): + test.start_server(get_app('native')) + + with test.table('table') as target: + text00 = target.cell.get_text(0, 0) + + assert target.get_current_page() == '1' + + target.click_current_page() + test.send_keys('100' + Keys.ENTER) + + assert target.get_current_page() == '100' + assert target.cell.get_text(0, 0) != text00 + + +def test_tpag005_ops_input_with_unfocus(test): + test.start_server(get_app('native')) + + with test.table('table') as target: + text00 = target.cell.get_text(0, 0) + + assert target.get_current_page() == '1' + + target.click_current_page() + test.send_keys('100') + target.cell.click(0, 0) + + assert target.get_current_page() == '100' + assert target.cell.get_text(0, 0) != text00 + + +@pytest.mark.parametrize('value,expected_value', [ + (0, 1), + (-1, 1), + ('a', 1), + (pages * 2, pages) +]) +def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): + test.start_server(get_app('native')) + + with test.table('table') as target: + text00 = target.cell.get_text(0, 0) + + assert target.get_current_page() == '1' + + target.click_current_page() + test.send_keys(str(value) + Keys.ENTER) + + assert target.get_current_page() == str(expected_value) + + +@pytest.mark.parametrize('value,expected_value', [ + (0, 1), + (-1, 1), + ('a', 1), + (pages * 2, pages) +]) +def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): + test.start_server(get_app('native')) + + with test.table('table') as target: + text00 = target.cell.get_text(0, 0) + + assert target.get_current_page() == '1' + + target.click_current_page() + test.send_keys(str(value)) + target.cell.click(0, 0) + + assert target.get_current_page() == str(expected_value) + + +@pytest.mark.parametrize('mode', ['custom', 'native']) +def test_tpag008_hide_with_single_page(test, mode): + test.start_server(get_app(mode=mode, data=df[0: PAGE_SIZE])) + + with test.table('table') as target: + assert not target.has_pagination() + + +def test_tpag009_hide_with_invalid_page_count(test): + test.start_server(get_app(mode='custom', page_count=-1)) + + with test.table('table') as target: + assert not target.has_pagination() + + +def test_tpag010_limits_page(test): + test.start_server(get_app(mode='custom', page_count=10)) + + with test.table('table') as target: + target.click_last_page() + + assert target.get_current_page() == '10' \ No newline at end of file From 89df40d5051971c2d9baaf05128c973f5c90d604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Thu, 20 Feb 2020 16:19:51 -0500 Subject: [PATCH 21/38] wrap paging ops in a paging facade --- tests/selenium/conftest.py | 18 +++++--- tests/selenium/test_pagination.py | 70 +++++++++++++++---------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index b1460c89c..bbbbf2878 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -156,16 +156,12 @@ def is_selected(self, row, state = _READY): )[row].find_element_by_css_selector('input').is_selected() -class DataTableFacade(object): +class DataTablePagingFacade(object): @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): self.id = id self.mixin = mixin - self.cell = DataTableCellFacade(id, mixin) - self.column = DataTableColumnFacade(id, mixin) - self.row = DataTableRowFacade(id, mixin) - def click_next_page(self): self.mixin._wait_for_table(self.id) @@ -250,6 +246,18 @@ def get_current_page(self): ).get_attribute('placeholder') +class DataTableFacade(object): + @preconditions(_validate_id, _validate_mixin) + def __init__(self, id, mixin): + self.id = id + self.mixin = mixin + + self.cell = DataTableCellFacade(id, mixin) + self.column = DataTableColumnFacade(id, mixin) + self.paging = DataTablePagingFacade(id, mixin) + self.row = DataTableRowFacade(id, mixin) + + class DataTableMixin(object): @preconditions(_validate_id, _validate_state) def _wait_for_table(self, id, state = _ANY): diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index 36c859489..fd8a4aecf 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -62,20 +62,20 @@ def test_tpag001_next_previous(test, mode): with test.table('table') as target: assert target.cell.get_text(0, 0) == '0' - assert target.has_next_page() - assert not target.has_prev_page() + assert target.paging.has_next_page() + assert not target.paging.has_prev_page() - target.click_next_page() + target.paging.click_next_page() assert target.cell.get_text(0, 0) == '5' - assert target.has_next_page() - assert target.has_prev_page() + assert target.paging.has_next_page() + assert target.paging.has_prev_page() - target.click_prev_page() + target.paging.click_prev_page() assert target.cell.get_text(0, 0) == '0' - assert target.has_next_page() - assert not target.has_prev_page() + assert target.paging.has_next_page() + assert not target.paging.has_prev_page() @pytest.mark.parametrize('mode', ['custom', 'native']) @@ -83,11 +83,11 @@ def test_tpag002_ops_on_first_page(test, mode): test.start_server(get_app(mode)) with test.table('table') as target: - assert target.get_current_page() == '1' - assert not target.has_first_page() - assert not target.has_prev_page() - assert target.has_next_page() - assert target.has_last_page() + assert target.paging.get_current_page() == '1' + assert not target.paging.has_first_page() + assert not target.paging.has_prev_page() + assert target.paging.has_next_page() + assert target.paging.has_last_page() @pytest.mark.parametrize('mode', ['custom', 'native']) @@ -95,13 +95,13 @@ def test_tpag003_ops_on_last_page(test, mode): test.start_server(get_app(mode)) with test.table('table') as target: - target.click_last_page() + target.paging.click_last_page() - assert target.get_current_page() == str(pages) - assert target.has_first_page() - assert target.has_prev_page() - assert not target.has_next_page() - assert not target.has_last_page() + assert target.paging.get_current_page() == str(pages) + assert target.paging.has_first_page() + assert target.paging.has_prev_page() + assert not target.paging.has_next_page() + assert not target.paging.has_last_page() def test_tpag004_ops_input_with_enter(test): @@ -110,12 +110,12 @@ def test_tpag004_ops_input_with_enter(test): with test.table('table') as target: text00 = target.cell.get_text(0, 0) - assert target.get_current_page() == '1' + assert target.paging.get_current_page() == '1' - target.click_current_page() + target.paging.click_current_page() test.send_keys('100' + Keys.ENTER) - assert target.get_current_page() == '100' + assert target.paging.get_current_page() == '100' assert target.cell.get_text(0, 0) != text00 @@ -125,13 +125,13 @@ def test_tpag005_ops_input_with_unfocus(test): with test.table('table') as target: text00 = target.cell.get_text(0, 0) - assert target.get_current_page() == '1' + assert target.paging.get_current_page() == '1' - target.click_current_page() + target.paging.click_current_page() test.send_keys('100') target.cell.click(0, 0) - assert target.get_current_page() == '100' + assert target.paging.get_current_page() == '100' assert target.cell.get_text(0, 0) != text00 @@ -147,12 +147,12 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): with test.table('table') as target: text00 = target.cell.get_text(0, 0) - assert target.get_current_page() == '1' + assert target.paging.get_current_page() == '1' - target.click_current_page() + target.paging.click_current_page() test.send_keys(str(value) + Keys.ENTER) - assert target.get_current_page() == str(expected_value) + assert target.paging.get_current_page() == str(expected_value) @pytest.mark.parametrize('value,expected_value', [ @@ -167,13 +167,13 @@ def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): with test.table('table') as target: text00 = target.cell.get_text(0, 0) - assert target.get_current_page() == '1' + assert target.paging.get_current_page() == '1' - target.click_current_page() + target.paging.click_current_page() test.send_keys(str(value)) target.cell.click(0, 0) - assert target.get_current_page() == str(expected_value) + assert target.paging.get_current_page() == str(expected_value) @pytest.mark.parametrize('mode', ['custom', 'native']) @@ -181,20 +181,20 @@ def test_tpag008_hide_with_single_page(test, mode): test.start_server(get_app(mode=mode, data=df[0: PAGE_SIZE])) with test.table('table') as target: - assert not target.has_pagination() + assert not target.paging.has_pagination() def test_tpag009_hide_with_invalid_page_count(test): test.start_server(get_app(mode='custom', page_count=-1)) with test.table('table') as target: - assert not target.has_pagination() + assert not target.paging.has_pagination() def test_tpag010_limits_page(test): test.start_server(get_app(mode='custom', page_count=10)) with test.table('table') as target: - target.click_last_page() + target.paging.click_last_page() - assert target.get_current_page() == '10' \ No newline at end of file + assert target.paging.get_current_page() == '10' \ No newline at end of file From a051880039a33d67d0d4c23290c20164d701531c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 13:49:14 -0500 Subject: [PATCH 22/38] cypress -> selenium, (non-)blocking edits --- package.json | 2 - tests/cypress/dash/v_data_loading.py | 88 -------- .../tests/server/loading_uneditable_test.ts | 194 ------------------ tests/selenium/conftest.py | 33 +-- tests/selenium/test_editable.py | 172 ++++++++++++++++ 5 files changed, 191 insertions(+), 298 deletions(-) delete mode 100644 tests/cypress/dash/v_data_loading.py delete mode 100644 tests/cypress/tests/server/loading_uneditable_test.ts create mode 100644 tests/selenium/test_editable.py diff --git a/package.json b/package.json index 996e5f9d1..c0e9b4bbd 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,10 @@ "private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"", "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", - "private::host_dash8084": "python tests/cypress/dash/v_data_loading.py", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", "private::wait_dash8083": "wait-on http://localhost:8083", - "private::wait_dash8084": "wait-on http://localhost:8084", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", diff --git a/tests/cypress/dash/v_data_loading.py b/tests/cypress/dash/v_data_loading.py deleted file mode 100644 index 68bccc066..000000000 --- a/tests/cypress/dash/v_data_loading.py +++ /dev/null @@ -1,88 +0,0 @@ -# pylint: disable=global-statement -import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate -import dash_html_components as html -import dash_core_components as dcc -import os -import pandas as pd -import sys -from time import sleep - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(x) for x in module_names] -dash_table = modules[0] - -url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" -df = pd.read_csv(url) -df = df.values - -app = dash.Dash() -app.css.config.serve_locally = True -app.scripts.config.serve_locally = True - -app.layout = html.Div( - [ - dcc.Input(id='change-data-property'), - dcc.Input(id='change-other-property'), - - dash_table.DataTable( - id="table", - data=df[0:250], - columns=[ - {"id": 0, "name": "Complaint ID", "hideable": True}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - editable=True, - sort_action='native', - include_headers_on_copy_paste=True, - - ) - ] -) - - -@app.callback( - Output("table", "style_cell_conditional"), - [Input("change-other-property", "value")] -) -def dontTriggerWait(to_change): - if to_change != 'dont_change_data': - raise PreventUpdate - - sleep(5) - return [] - - -@app.callback( - Output("table", "data"), - [Input("change-data-property", "value")] -) -# pylint: disable=unused-argument -def triggerWait(to_change): - if to_change != 'change_data': - raise PreventUpdate - - sleep(5) - return df[0:250] - - -if __name__ == "__main__": - app.run_server(port=8084, debug=False) diff --git a/tests/cypress/tests/server/loading_uneditable_test.ts b/tests/cypress/tests/server/loading_uneditable_test.ts deleted file mode 100644 index 494dba2f8..000000000 --- a/tests/cypress/tests/server/loading_uneditable_test.ts +++ /dev/null @@ -1,194 +0,0 @@ -import DashTable, { State } from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; - -describe('loading states uneditable', () => { - - beforeEach(() => { - cy.visit('http://localhost:8084'); - }); - - it('prevents editing while loading', () => { - // Table is editable - DashTable - .getCell(0, 0) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - // Trigger data callback - cy.get('#change-data-property').click(); - DOM.focused.type(`change_data${Key.Enter}`); - - // Table is not editable - DashTable - .getCell(0, 0, State.Loading) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 0); - - DashTable - .getCell(0, 0, State.Any) - .within(() => cy.get('.dash-cell-value').should('not.have.html', 'Hello')); - - cy.get('#change-data-property').should('have.value', 'change_data'); - - cy.wait(5000); - - // Table is editable - DashTable - .getCell(0, 0, State.Ready) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - DOM.focused.type(`Hello${Key.Enter}`); - - DashTable - .getCell(0, 0) - .within(() => cy.get('.dash-cell-value') - .should('have.html', 'Hello')); - }); - - it('keeps focus on callback completion', () => { - cy.get('#change-data-property').click(); - DOM.focused.type(`change_data${Key.Enter}`); - - DashTable.getCell(0, 0, State.Loading).click(); - cy.wait(5000); - DashTable.getCell(0, 0, State.Ready); - - DOM.focused.type(`Hello${Key.Enter}`); - DashTable - .getCell(0, 0) - .within(() => cy.get('.dash-cell-value') - .should('have.html', 'Hello')); - }); - - it('does not steal focus on callback completion', () => { - DashTable.getCell(0, 0, State.Ready).click(); - - cy.get('#change-data-property').click(); - DOM.focused.type(`change_data${Key.Enter}`); - - DashTable.getCell(0, 0, State.Loading); - DOM.focused.should('have.id', 'change-data-property'); - - cy.wait(5000); - - DashTable.getCell(0, 0, State.Ready); - DOM.focused.should('have.id', 'change-data-property'); - }); - - it('permits editing when a non-data prop is being changed', () => { - // Table is editable - DashTable - .getCell(0, 0) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - // Trigger non-data callback - cy.get('#change-other-property').click(); - DOM.focused.type(`dont_change_data${Key.Enter}`); - - // Table is editable - DashTable - .getCell(0, 0) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - DOM.focused.type(`Hello${Key.Enter}`); - DashTable.getCell(1, 0).click(); - - DashTable - .getCell(0, 0) - .within(() => cy.get('.dash-cell-value') - .should('have.html', 'Hello')); - }); - - it('does not permit copy-paste when data are loading', () => { - // Table is editable - DashTable - .getCell(0, 0) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - // Trigger data callback - cy.get('#change-data-property').click(); - DOM.focused.type(`change_data${Key.Enter}`); - - // Table is not editable - DashTable - .getCell(0, 0, State.Loading) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 0); - - DOM.focused.type(`${Key.Meta}c`); - - DashTable - .getCell(0, 1, State.Loading) - .click(); - - DashTable - .getCell(0, 1, State.Loading) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 0); - - DOM.focused.type(`${Key.Meta}v`); - - DashTable - .getCell(0, 0, State.Loading) - .click(); - - DashTable.getCell(0, 1, State.Loading) - .within(() => cy.get('.dash-cell-value') - .should('not.have.html', '0')); - - cy.wait(5000); - - // Table is editable - DashTable - .getCell(0, 1) - .click(); - - DOM.focused.type(`${Key.Meta}v`); - - DashTable - .getCell(0, 0) - .click(); - - DashTable.getCell(0, 1) - .within(() => cy.get('.dash-cell-value') - .should('have.html', '0')); - }); - - it('permits copy-paste when a non-data prop is loading', () => { - // Table is editable - DashTable - .getCell(0, 0) - .click() - .find('.dash-input-cell-value-container > input').should('have.length', 1); - - // Trigger non-data callback - cy.get('#change-other-property').click(); - DOM.focused.type(`dont_change_data${Key.Enter}`); - - DashTable - .getCell(0, 0) - .click(); - - DOM.focused.type(`${Key.Meta}c`); - - DashTable - .getCell(0, 1) - .click(); - - DOM.focused.type(`${Key.Meta}v`); - - DashTable - .getCell(0, 0) - .click(); - - DashTable.getCell(0, 1) - .within(() => cy.get('.dash-cell-value') - .should('have.html', '0')); - }); - -}); diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index bbbbf2878..f6eb733c9 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -55,23 +55,23 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_col, _validate_state) - def _get_cell_value(self, row, col, selector, state = _READY): + def _get_cell_value(self, row, col, selector, state = _ANY): return self.get(row, col, state).find_element_by_css_selector('.dash-cell-value') @preconditions(_validate_row, _validate_col, _validate_state) - def click(self, row, col, state = _READY): + def click(self, row, col, state = _ANY): return self.get(row, col, state).click() @preconditions(_validate_row, _validate_col, _validate_state) - def double_click(self, row, col, state = _READY): + def double_click(self, row, col, state = _ANY): ac = ActionChains(self.mixin.driver) ac.move_to_element(self._get_cell_value(row, col, state)) - ac.pause(1) + ac.pause(1) # sometimes experiencing incorrect behavior on scroll otherwise ac.double_click() return ac.perform() @preconditions(_validate_row, _validate_col, _validate_state) - def get(self, row, col, state = _READY): + def get(self, row, col, state = _ANY): self.mixin._wait_for_table(self.id, state) return self.mixin.find_element( @@ -81,7 +81,7 @@ def get(self, row, col, state = _READY): ) @preconditions(_validate_row, _validate_col, _validate_state) - def get_text(self, row, col, state = _READY): + def get_text(self, row, col, state = _ANY): el = self._get_cell_value(row, col, state) value = el.get_attribute('value') @@ -92,13 +92,13 @@ def get_text(self, row, col, state = _READY): ) @preconditions(_validate_row, _validate_col, _validate_state) - def is_active(self, row, col, state = _READY): + def is_active(self, row, col, state = _ANY): input = self.get(row, col, state).find_element_by_css_selector('input') return 'focused' in input.get_attribute('class').split(' ') @preconditions(_validate_row, _validate_col, _validate_state) - def is_focused(self, row, col, state = _READY): + def is_focused(self, row, col, state = _ANY): cell = self.get(row, col, state) return 'focused' in cell.get_attribute('class').split(' ') @@ -111,7 +111,7 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_col_id, _validate_state) - def get(self, row, col_id, state = _READY): + def get(self, row, col_id, state = _ANY): self.mixin._wait_for_table(self.id, state) return self.mixin.find_elements( @@ -119,13 +119,13 @@ def get(self, row, col_id, state = _READY): )[row] @preconditions(_validate_row, _validate_col_id, _validate_state) - def hide(self, row, col_id, state = _READY): + def hide(self, row, col_id, state = _ANY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--hide' ).click() @preconditions(_validate_row, _validate_col_id, _validate_state) - def sort(self, row, col_id, state = _READY): + def sort(self, row, col_id, state = _ANY): self.get(row, col_id, state).find_element_by_css_selector( '.column-header--sort' ).click() @@ -138,19 +138,19 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_state) - def delete(self, row, state = _READY): + def delete(self, row, state = _ANY): return self.mixin.find_elements( '#{} {} tbody tr td.dash-delete-cell'.format(self.id, state) )[row].click() @preconditions(_validate_row, _validate_state) - def select(self, row, state = _READY): + def select(self, row, state = _ANY): return self.mixin.find_elements( '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) )[row].click() @preconditions(_validate_row, _validate_state) - def is_selected(self, row, state = _READY): + def is_selected(self, row, state = _ANY): return self.mixin.find_elements( '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) )[row].find_element_by_css_selector('input').is_selected() @@ -257,6 +257,11 @@ def __init__(self, id, mixin): self.paging = DataTablePagingFacade(id, mixin) self.row = DataTableRowFacade(id, mixin) + def is_ready(self): + return self.mixin._wait_for_table(self.id, _READY) + + def is_loading(self): + return self.mixin._wait_for_table(self.id, _LOADING) class DataTableMixin(object): @preconditions(_validate_id, _validate_state) diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py new file mode 100644 index 000000000..72365b349 --- /dev/null +++ b/tests/selenium/test_editable.py @@ -0,0 +1,172 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +from multiprocessing import Lock +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import pandas as pd + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url) +df = rawDf.to_dict('rows') + +def get_app_and_locks(): + app = dash.Dash(__name__) + + app.layout = html.Div([ + dcc.Input(id='input'), + html.Button(['Blocking'], id='blocking'), + html.Button(['Non Blocking'], id='non-blocking'), + DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=df, + editable=True, + filter_action="native", + fixed_columns={ 'headers': True }, + fixed_rows={ 'headers': True }, + page_action="native", + row_deletable=True, + row_selectable=True, + sort_action="native", + ) + ]) + + blocking_lock = Lock() + non_blocking_lock = Lock() + + @app.callback( + Output("table", "style_cell_conditional"), + [Input("non-blocking", "n_clicks")] + ) + def non_blocking_callback(clicks): + if clicks is None: + raise PreventUpdate + + with non_blocking_lock: + return [] + + @app.callback( + Output("table", "data"), + [Input("blocking", "n_clicks")] + ) + def blocking_callback(clicks): + if clicks is None: + raise PreventUpdate + + print('callback > blocking') + with blocking_lock: + print('callback > blocking > obtained!') + return df + + return app, blocking_lock, non_blocking_lock + + +def test_tedi001_loading_on_data_change(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with blocking: + test.find_element('#blocking').click() + target.is_loading() + target.cell.click(0, 0) + assert len(target.cell.get(0, 0).find_elements_by_css_selector('input')) == 0 + + target.is_ready() + assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + + +def test_tedi002_ready_on_non_data_change(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with blocking: + test.find_element('#non-blocking').click() + target.is_ready() + target.cell.click(0, 0) + assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + + target.is_ready() + assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + + +def test_tedi003_does_not_steal_focus(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with blocking: + test.find_element('#blocking').click() + test.find_element('#input').click() + assert test.find_element('#input') == test.driver.switch_to.active_element + + target.is_ready() + assert test.find_element('#input') == test.driver.switch_to.active_element + + +def test_tedi004_edit_on_non_blocking(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with blocking: + test.find_element('#non-blocking').click() + target.cell.click(0, 0) + test.send_keys('abc' + Keys.ENTER) + assert target.cell.get_text(0, 0) == 'abc' + + +def test_tedi005_prevent_copy_paste_on_blocking(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with blocking: + test.find_element('#blocking').click() + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.RIGHT) + + test.copy() + target.cell.click(2, 0) + test.paste() + + for row in range(2): + for col in range(2): + assert target.cell.get_text(row + 2, col) != target.cell.get_text(row, col) + + +def test_tedi006_allow_copy_paste_on_non_blocking(test): + app, blocking, non_blocking = get_app_and_locks() + + test.start_server(app) + + with test.table('table') as target: + with non_blocking: + test.find_element('#non-blocking').click() + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.RIGHT) + + test.copy() + target.cell.click(2, 0) + test.paste() + + for row in range(2): + for col in range(2): + assert target.cell.get_text(row + 2, col) == target.cell.get_text(row, col) + + From 5f8c0ee8e8b00de79659e38e9f4376b1cab3329c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 13:49:23 -0500 Subject: [PATCH 23/38] paging --- tests/selenium/test_basic_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index fa6a8f810..45affe06a 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -40,7 +40,7 @@ def test_tbst001_get_cell(test): with test.table('table') as target: assert target.cell.get_text(0, 0) == '0' - target.click_next_page() + target.paging.click_next_page() assert target.cell.get_text(0, 0) == '250' From 425a995101cf775d12c1c18c730bf62859ea3d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 14:08:40 -0500 Subject: [PATCH 24/38] remove print statements --- tests/selenium/test_editable.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py index 72365b349..6e7088090 100644 --- a/tests/selenium/test_editable.py +++ b/tests/selenium/test_editable.py @@ -60,9 +60,7 @@ def blocking_callback(clicks): if clicks is None: raise PreventUpdate - print('callback > blocking') with blocking_lock: - print('callback > blocking > obtained!') return df return app, blocking_lock, non_blocking_lock @@ -168,5 +166,3 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): for row in range(2): for col in range(2): assert target.cell.get_text(row + 2, col) == target.cell.get_text(row, col) - - From 69fb619892440f4637e151690a287b4e97d28b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 15:59:19 -0500 Subject: [PATCH 25/38] cypress --> selenium; derived props --- package.json | 2 - tests/cypress/dash/v_fe_page.py | 95 ------ .../cypress/tests/server/select_props_test.ts | 173 ----------- tests/selenium/conftest.py | 5 + tests/selenium/test_derived_props.py | 288 ++++++++++++++++++ 5 files changed, 293 insertions(+), 270 deletions(-) delete mode 100644 tests/cypress/dash/v_fe_page.py delete mode 100644 tests/cypress/tests/server/select_props_test.ts create mode 100644 tests/selenium/test_derived_props.py diff --git a/package.json b/package.json index c0e9b4bbd..9d7feaec5 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,9 @@ "private::build:js-test-standalone": "run-s \"private::build -- --mode development --config webpack.test.standalone.config.js\"", "private::build:js-test-watch": "run-s \"private::build -- --mode development --config webpack.test.config.js --watch\"", "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", - "private::host_dash8083": "python tests/cypress/dash/v_fe_page.py", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", - "private::wait_dash8083": "wait-on http://localhost:8083", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", diff --git a/tests/cypress/dash/v_fe_page.py b/tests/cypress/dash/v_fe_page.py deleted file mode 100644 index 0f6b97cc0..000000000 --- a/tests/cypress/dash/v_fe_page.py +++ /dev/null @@ -1,95 +0,0 @@ -# pylint: disable=global-statement -import json -import os -import pandas as pd -import sys - -import dash -from dash.dependencies import Input, Output -import dash_html_components as html - -sys.path.append( - os.path.abspath( - os.path.join(os.path.dirname(sys.argv[0]), - os.pardir, os.pardir, os.pardir) - ) -) -module_names = ["dash_table"] -modules = [__import__(module) for module in module_names] -dash_table = modules[0] - -url = ("https://github.com/plotly/datasets/raw/master/" - "26k-consumer-complaints.csv") -df = pd.read_csv(url, nrows=1000) -# add IDs that don't match but are easily derivable from row #s -data = [ - {k: v for k, v in list(enumerate(row)) + [('id', i + 3000)]} - for i, row in enumerate(df.values) -] - -app = dash.Dash() -app.css.config.serve_locally = True -app.scripts.config.serve_locally = True - -app.layout = html.Div( - [ - dash_table.DataTable( - id="table", - data=data, - page_action="native", - page_current=0, - page_size=250, - columns=[ - {"id": 0, "name": "Complaint ID"}, - {"id": 1, "name": "Product"}, - {"id": 2, "name": "Sub-product"}, - {"id": 3, "name": "Issue"}, - {"id": 4, "name": "Sub-issue"}, - {"id": 5, "name": "State"}, - {"id": 6, "name": "ZIP"}, - {"id": 7, "name": "code"}, - {"id": 8, "name": "Date received"}, - {"id": 9, "name": "Date sent to company"}, - {"id": 10, "name": "Company"}, - {"id": 11, "name": "Company response"}, - {"id": 12, "name": "Timely response?"}, - {"id": 13, "name": "Consumer disputed?"}, - ], - fixed_columns={ 'headers': True }, - fixed_rows={ 'headers': True }, - row_selectable=True, - row_deletable=True, - sort_action="native", - filter_action='native', - editable=True, - ), - html.Div(id="props_container") - ] -) - -props = [ - 'active_cell', 'start_cell', 'end_cell', 'selected_cells', - 'selected_rows', 'selected_row_ids', - 'derived_viewport_selected_rows', 'derived_viewport_selected_row_ids', - 'derived_virtual_selected_rows', 'derived_virtual_selected_row_ids', - 'derived_viewport_indices', 'derived_viewport_row_ids', - 'derived_virtual_indices', 'derived_virtual_row_ids' -] - - -@app.callback( - Output("props_container", "children"), - [Input("table", prop) for prop in props] -) -def show_props(*args): - return html.Table([ - html.Tr([ - html.Td(prop), - html.Td(json.dumps(val), id=prop + '_container') - ]) - for prop, val in zip(props, args) - ]) - - -if __name__ == "__main__": - app.run_server(port=8083, debug=False) diff --git a/tests/cypress/tests/server/select_props_test.ts b/tests/cypress/tests/server/select_props_test.ts deleted file mode 100644 index f42c6acec..000000000 --- a/tests/cypress/tests/server/select_props_test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import DashTable from 'cypress/DashTable'; -import DOM from 'cypress/DOM'; -import Key from 'cypress/Key'; -import { map, xprod } from 'ramda'; - -function expectArray(selector: string, vals: number[], sorted?: boolean) { - cy.get(selector).should($container => { - const valsOut = JSON.parse($container.text()) as number[]; - if (sorted !== false) { - valsOut.sort(); - } - expect(valsOut).to.deep.equal(vals); - }); -} - -function expectCellSelection( - rows: number[], - rowIds?: number[], - cols?: number[], - colIds?: number[], - activeItem?: number[], // indices within rows/cols, dflt [0,0] - startItem?: number[] // same^ -) { - function makeCell(rc: number[]) { - const [r, c] = rc; - return { - row: rows[r], - row_id: rowIds && rowIds[r], - column: cols && cols[c], - column_id: colIds && colIds[c] - }; - } - - let activeCell: any; - let startCell: any; - let endCell: any; - let selectedCells: any; - if (rows.length && cols) { - activeCell = makeCell(activeItem || [0, 0]); - startCell = makeCell(startItem || [0, 0]); - endCell = makeCell(startItem ? [0, 0] : [rows.length - 1, cols.length - 1]); - selectedCells = map(makeCell, xprod(range(0, rows.length - 1), range(0, cols.length - 1))); - } else { - activeCell = startCell = endCell = selectedCells = null; - } - - cy.get('#active_cell_container').should($container => { - expect(JSON.parse($container.text())).to.deep.equal(activeCell); - }); - cy.get('#start_cell_container').should($container => { - expect(JSON.parse($container.text())).to.deep.equal(startCell); - }); - cy.get('#end_cell_container').should($container => { - expect(JSON.parse($container.text())).to.deep.equal(endCell); - }); - cy.get('#selected_cells_container').should($container => { - if (selectedCells && selectedCells.length) { - expect(JSON.parse($container.text())).to.deep.equal(selectedCells); - } else { - expect($container.text()).to.be.oneOf(['null', '[]']); - } - }); -} - -// NOTE: this function includes both endpoints -// easier to compare with the full arrays that way. -function range(from: number, to: number, step?: number) { - const _step = step || 1; - const out: number[] = []; - for (let v = from; v * _step <= to * _step; v += _step) { - out.push(v); - } - return out; -} - -describe('select row', () => { - describe('fe pagination & sort', () => { - beforeEach(() => cy.visit('http://localhost:8083')); - - it('selection props are correct, no sort / filter', () => { - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(1).within(() => cy.get('input').click()); - - expectCellSelection([]); - - // single cell selection - DashTable.getCell(3, 1).click(); - expectCellSelection([3], [3003], [1], [1]); - - // region up & left - active & start stay at the bottom right - DOM.focused.type(Key.Shift, { release: false }); - DashTable.getCell(1, 0).click(); - expectCellSelection([1, 2, 3], [3001, 3002, 3003], [0, 1], [0, 1], [2, 1], [2, 1]); - - // shrink the selection - DashTable.getCell(2, 1).click(); - expectCellSelection([2, 3], [3002, 3003], [1], [1], [1, 0], [1, 0]); - - // move the active cell without changing the selection - DOM.focused.type(Key.Shift); // and release - DashTable.getCell(2, 1).click(); - expectCellSelection([2, 3], [3002, 3003], [1], [1], [0, 0], [1, 0]); - - expectArray('#selected_rows_container', [0, 1]); - expectArray('#selected_row_ids_container', [3000, 3001]); - expectArray('#derived_viewport_selected_rows_container', [0, 1]); - expectArray('#derived_viewport_selected_row_ids_container', [3000, 3001]); - expectArray('#derived_virtual_selected_rows_container', [0, 1]); - expectArray('#derived_virtual_selected_row_ids_container', [3000, 3001]); - expectArray('#derived_viewport_indices_container', range(0, 249), false); - expectArray('#derived_viewport_row_ids_container', range(3000, 3249), false); - expectArray('#derived_virtual_indices_container', range(0, 999), false); - expectArray('#derived_virtual_row_ids_container', range(3000, 3999), false); - }); - - it('selection props are correct, with filter', () => { - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(1).within(() => cy.get('input').click()); - DashTable.getSelect(2).within(() => cy.get('input').click()); - - cy.get('tr th.column-0.dash-filter input').type(`is even${Key.Enter}`); - - // filtered-out data is still selected - expectArray('#selected_rows_container', [0, 1, 2]); - expectArray('#selected_row_ids_container', [3000, 3001, 3002]); - expectArray('#derived_viewport_selected_rows_container', [0, 1]); - expectArray('#derived_viewport_selected_row_ids_container', [3000, 3002]); - expectArray('#derived_virtual_selected_rows_container', [0, 1]); - expectArray('#derived_virtual_selected_row_ids_container', [3000, 3002]); - expectArray('#derived_viewport_indices_container', range(0, 498, 2), false); - expectArray('#derived_viewport_row_ids_container', range(3000, 3498, 2), false); - expectArray('#derived_virtual_indices_container', range(0, 998, 2), false); - expectArray('#derived_virtual_row_ids_container', range(3000, 3998, 2), false); - }); - - it('selection props are correct, with filter & sort', () => { - DashTable.getSelect(0).within(() => cy.get('input').click()); - DashTable.getSelect(1).within(() => cy.get('input').click()); - - DashTable.getCell(3, 1).click(); - expectCellSelection([3], [3003], [1], [1]); - - cy.get('tr th.column-0.dash-filter input').type(`is even${Key.Enter}`); - - expectCellSelection([]); - - DashTable.getCell(3, 1).click(); - expectCellSelection([3], [3006], [1], [1]); - - cy.get('tr th.column-0 .column-header--sort').last().click({ force: true }); - - expectCellSelection([]); - - cy.get('tr th.column-0 .column-header--sort').last().click({ force: true }); - - DashTable.getSelect(0).within(() => cy.get('input').click()); - - DashTable.getCell(3, 1).click(); - expectCellSelection([3], [3992], [1], [1]); - - expectArray('#selected_rows_container', [0, 1, 998]); - expectArray('#selected_row_ids_container', [3000, 3001, 3998]); - expectArray('#derived_viewport_selected_rows_container', [0]); - expectArray('#derived_viewport_selected_row_ids_container', [3998]); - expectArray('#derived_virtual_selected_rows_container', [0, 499]); - expectArray('#derived_virtual_selected_row_ids_container', [3000, 3998]); - expectArray('#derived_viewport_indices_container', range(998, 500, -2), false); - expectArray('#derived_viewport_row_ids_container', range(3998, 3500, -2), false); - expectArray('#derived_virtual_indices_container', range(998, 0, -2), false); - expectArray('#derived_virtual_row_ids_container', range(3998, 3000, -2), false); - }); - }); -}); diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index f6eb733c9..edbab4704 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -130,6 +130,11 @@ def sort(self, row, col_id, state = _ANY): '.column-header--sort' ).click() + @preconditions(_validate_col_id, _validate_state) + def filter(self, col_id, state = _ANY): + return self.mixin.find_element( + '#{} {} tbody tr th.dash-filter[data-dash-column="{}"]'.format(self.id, state, col_id) + ).click() class DataTableRowFacade(object): @preconditions(_validate_id, _validate_mixin) diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py new file mode 100644 index 000000000..0baa6d38a --- /dev/null +++ b/tests/selenium/test_derived_props.py @@ -0,0 +1,288 @@ +import dash +from dash.dependencies import Input, Output, State +from dash.exceptions import PreventUpdate + +import dash_core_components as dcc +import dash_html_components as html +from dash_table import DataTable + +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.common.action_chains import ActionChains + +import json +import pandas as pd +import time + +url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" +rawDf = pd.read_csv(url, nrows=100) +rawDf['id'] = rawDf.index + 3000 + +df = rawDf.to_dict('rows') + +props = [ + 'active_cell', + 'start_cell', + 'end_cell', + 'selected_cells', + 'selected_rows', + 'selected_row_ids', + 'derived_viewport_selected_rows', + 'derived_viewport_selected_row_ids', + 'derived_virtual_selected_rows', + 'derived_virtual_selected_row_ids', + 'derived_viewport_indices', + 'derived_viewport_row_ids', + 'derived_virtual_indices', + 'derived_virtual_row_ids' +] + +def get_app(): + app = dash.Dash(__name__) + + app.layout = html.Div([ + DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=df, + editable=True, + filter_action="native", + fixed_columns={ 'headers': True }, + fixed_rows={ 'headers': True }, + page_action="native", + page_size=10, + row_deletable=True, + row_selectable=True, + sort_action="native", + ), + html.Div(id='props_container', children=['Nothing yet']) + ]) + + @app.callback( + Output("props_container", "children"), + [Input("table", prop) for prop in props] + ) + def show_props(*args): + # return 'Something yet!' + # print('show props') + return html.Table([ + html.Tr([ + html.Td(prop), + html.Td(json.dumps(val) if val is not None else 'None', id=prop) + ]) + for prop, val in zip(props, args) + ]) + + return app + + +def test_tdrp001_select_rows(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.row.select(0) + target.row.select(1) + + assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + +def test_tdrp002_select_cell(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 0) + + active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) + + assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps([active]) + assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + +def test_tdrp003_select_cells(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) + + active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) + + selected = [] + for row in range(3): + for col in range(3): + selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) + + assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) + assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) + assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) + assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + # reduce selection + with test.hold(Keys.SHIFT): + test.send_keys(Keys.UP + Keys.LEFT) + + selected = [] + for row in range(2): + for col in range(2): + selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) + + assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) + assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) + assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) + assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + +def test_tdrp004_navigate_selected_cells(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) + + selected = [] + for row in range(3): + for col in range(3): + selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) + + for row in range(3): + for col in range(3): + active = dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000) + + assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) + assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) + assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) + assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) + assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + test.send_keys(Keys.TAB) + + +def test_tdrp005_filtered_and_sorted_row_select(test): + test.start_server(get_app()) + + with test.table('table') as target: + target.row.select(0) + target.row.select(1) + target.row.select(2) + + assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + + target.column.filter(rawDf.columns[0]) + test.send_keys('is even' + Keys.ENTER) + + assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(0, 2))) + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 20, 2))) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3020, 2))) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(0, 2))) + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 100, 2))) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100, 2))) + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) + assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) + + assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] + assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(80, 100, 2))[::-1]) + assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3080, 3100, 2))[::-1]) + + assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(48, 50))[::-1]) + assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 100, 2))[::-1]) + assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100, 2))[::-1]) From 0687326f42b84ea5b9709e3a7981ccba30123bce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 16:24:49 -0500 Subject: [PATCH 26/38] remove legacy server tests --- .circleci/config.yml | 49 -------------------------------------------- package.json | 2 -- 2 files changed, 51 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e82b5986a..0b5a1cc22 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,55 +1,6 @@ version: 2 jobs: - "legacy-server-test": - docker: - - image: circleci/python:3.6.9-node-browsers - - image: cypress/base:10 - - steps: - - checkout - - restore_cache: - key: deps1-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} - - run: - name: Install npm packages - command: npm ci - - run: - name: Cypress Install - command: | - $(npm bin)/cypress install - - - save_cache: - key: deps1-{{ .Branch }}-{{ checksum "package-lock.json" }}-{{ checksum "package.json" }}-{{ checksum ".circleci/config.yml" }} - paths: - - node_modules - - /home/circleci/.cache/Cypress - - - run: - name: Install requirements - command: | - sudo pip install --upgrade virtualenv - python -m venv venv || virtualenv venv - . venv/bin/activate - pip install -r dev-requirements.txt --quiet - git clone --depth 1 git@github.com:plotly/dash.git dash-main - pip install -e ./dash-main[dev] --quiet - cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. - - - run: - name: Build - command: | - . venv/bin/activate - npm run private::build:js-test - npm run private::build:py - pip install -e . - - - run: - name: Run tests - command: | - . venv/bin/activate - npm run test.server-legacy - - "server-test": docker: - image: circleci/python:3.6-node-browsers diff --git a/package.json b/package.json index 9d7feaec5..fc595a627 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,12 @@ "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", "private::test.unit": "cypress run --browser chrome --spec 'tests/cypress/tests/unit/**/*'", - "private::test.server": "cypress run --browser chrome --spec 'tests/cypress/tests/server/**/*'", "private::test.standalone": "cypress run --browser chrome --spec 'tests/cypress/tests/standalone/**/*'", "build.watch": "webpack-dev-server --content-base dash_table --mode development --config webpack.dev.config.js", "build": "run-s private::build:js private::build:py", "postbuild": "es-check es5 dash_table/*.js", "format": "run-s \"private::lint:ts -- --fix\"", "lint": "run-s private::lint:*", - "test.server-legacy": "run-p --race private::host* private::test.server", "test.server": "pytest tests/selenium", "test.standalone": "run-p --race private::host_js private::test.standalone", "test.unit": "run-s private::test.python private::test.unit", From 5b51a02a9199d77ef8e65fb77b41434557a8ad57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 21 Feb 2020 16:27:16 -0500 Subject: [PATCH 27/38] remove legacy server tests take 2 --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b5a1cc22..7af833020 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -248,7 +248,6 @@ workflows: jobs: - "python-3.6" - "node" - - "legacy-server-test" - "server-test" - "standalone-test" - "unit-test" From 3d9cf3701730dd216cf0af1c41a4ff67ffba4153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:07:57 -0500 Subject: [PATCH 28/38] Black & Flake8 on all python files --- .flake8 | 5 + dash_table_base/Format.py | 273 ++++---- dash_table_base/FormatTemplate.py | 9 +- dash_table_base/__init__.py | 111 +-- dev-requirements.txt | 1 + index.py | 3 +- package.json | 4 +- setup.py | 10 +- .../review_app/test_app_df_backend_paging.py | 33 +- .../review_app/test_app_df_graph.py | 12 +- tests/integration/test_table_export_csv.py | 2 +- tests/selenium/conftest.py | 165 ++--- tests/selenium/test_basic_copy_paste.py | 85 ++- tests/selenium/test_basic_operations.py | 132 ++-- tests/selenium/test_derived_props.py | 662 +++++++++++++----- tests/selenium/test_editable.py | 106 +-- tests/selenium/test_markdown_copy_paste.py | 70 +- tests/selenium/test_pagination.py | 106 ++- tests/unit/format_test.py | 107 +-- 19 files changed, 1097 insertions(+), 799 deletions(-) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..f994d595b --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = E203, E266, E501, E731, W503 +max-line-length = 88 +max-complexity = 18 +select = B,C,E,F,W,T4 \ No newline at end of file diff --git a/dash_table_base/Format.py b/dash_table_base/Format.py index 4ae06ace3..8dd12638c 100644 --- a/dash_table_base/Format.py +++ b/dash_table_base/Format.py @@ -5,111 +5,97 @@ def get_named_tuple(name, dict): return collections.namedtuple(name, dict.keys())(*dict.values()) -Align = get_named_tuple('align', { - 'default': '', - 'left': '<', - 'right': '>', - 'center': '^', - "right_sign": '=' -}) - -Group = get_named_tuple('group', { - 'no': '', - 'yes': ',' -}) - -Padding = get_named_tuple('padding', { - 'no': '', - 'yes': '0' -}) - -Prefix = get_named_tuple('prefix', { - 'yocto': 10 ** -24, - 'zepto': 10 ** -21, - 'atto': 10 ** -18, - 'femto': 10 ** -15, - 'pico': 10 ** -12, - 'nano': 10 ** -9, - 'micro': 10 ** -6, - 'milli': 10 ** -3, - 'none': None, - 'kilo': 10 ** 3, - 'mega': 10 ** 6, - 'giga': 10 ** 9, - 'tera': 10 ** 12, - 'peta': 10 ** 15, - 'exa': 10 ** 18, - 'zetta': 10 ** 21, - 'yotta': 10 ** 24 -}) - -Scheme = get_named_tuple('scheme', { - 'default': '', - 'decimal': 'r', - 'decimal_integer': 'd', - 'decimal_or_exponent': 'g', - 'decimal_si_prefix': 's', - 'exponent': 'e', - 'fixed': 'f', - 'percentage': '%', - 'percentage_rounded': 'p', - 'binary': 'b', - 'octal': 'o', - 'lower_case_hex': 'x', - 'upper_case_hex': 'X', - 'unicode': 'c' -}) - -Sign = get_named_tuple('sign', { - 'default': '', - 'negative': '-', - 'positive': '+', - 'parantheses': '(', - 'space': ' ' -}) - -Symbol = get_named_tuple('symbol', { - 'no': '', - 'yes': '$', - 'binary': '#b', - 'octal': '#o', - 'hex': '#x' -}) - -Trim = get_named_tuple('trim', { - 'no': '', - 'yes': '~' -}) - - -class Format(): +Align = get_named_tuple( + "align", + {"default": "", "left": "<", "right": ">", "center": "^", "right_sign": "="}, +) + +Group = get_named_tuple("group", {"no": "", "yes": ","}) + +Padding = get_named_tuple("padding", {"no": "", "yes": "0"}) + +Prefix = get_named_tuple( + "prefix", + { + "yocto": 10 ** -24, + "zepto": 10 ** -21, + "atto": 10 ** -18, + "femto": 10 ** -15, + "pico": 10 ** -12, + "nano": 10 ** -9, + "micro": 10 ** -6, + "milli": 10 ** -3, + "none": None, + "kilo": 10 ** 3, + "mega": 10 ** 6, + "giga": 10 ** 9, + "tera": 10 ** 12, + "peta": 10 ** 15, + "exa": 10 ** 18, + "zetta": 10 ** 21, + "yotta": 10 ** 24, + }, +) + +Scheme = get_named_tuple( + "scheme", + { + "default": "", + "decimal": "r", + "decimal_integer": "d", + "decimal_or_exponent": "g", + "decimal_si_prefix": "s", + "exponent": "e", + "fixed": "f", + "percentage": "%", + "percentage_rounded": "p", + "binary": "b", + "octal": "o", + "lower_case_hex": "x", + "upper_case_hex": "X", + "unicode": "c", + }, +) + +Sign = get_named_tuple( + "sign", + {"default": "", "negative": "-", "positive": "+", "parantheses": "(", "space": " "}, +) + +Symbol = get_named_tuple( + "symbol", {"no": "", "yes": "$", "binary": "#b", "octal": "#o", "hex": "#x"} +) + +Trim = get_named_tuple("trim", {"no": "", "yes": "~"}) + + +class Format: def __init__(self, **kwargs): self._locale = {} - self._nully = '' + self._nully = "" self._prefix = Prefix.none self._specifier = { - 'align': Align.default, - 'fill': '', - 'group': Group.no, - 'width': '', - 'padding': Padding.no, - 'precision': '', - 'sign': Sign.default, - 'symbol': Symbol.no, - 'trim': Trim.no, - 'type': Scheme.default + "align": Align.default, + "fill": "", + "group": Group.no, + "width": "", + "padding": Padding.no, + "precision": "", + "sign": Sign.default, + "symbol": Symbol.no, + "trim": Trim.no, + "type": Scheme.default, } valid_methods = [ - m for m in dir(self.__class__) - if m[0] != '_' and m != 'to_plotly_json' + m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json" ] for kw, val in kwargs.items(): if kw not in valid_methods: raise TypeError( - '{0} is not a format method. Expected one of'.format(kw), - str(list(valid_methods)) + "{0} is not a format method. Expected one of".format(kw), + str(list(valid_methods)), ) getattr(self, kw)(val) @@ -118,38 +104,37 @@ def _validate_char(self, value): self._validate_string(value) if len(value) != 1: - raise ValueError('expected value to a string of length one') + raise ValueError("expected value to a string of length one") def _validate_non_negative_integer_or_none(self, value): if value is None: return if not isinstance(value, int): - raise TypeError('expected value to be an integer') + raise TypeError("expected value to be an integer") if value < 0: - raise ValueError('expected value to be non-negative', str(value)) + raise ValueError("expected value to be non-negative", str(value)) def _validate_named(self, value, named_values): if value not in named_values: - raise TypeError('expected value to be one of', - str(list(named_values))) + raise TypeError("expected value to be one of", str(list(named_values))) def _validate_string(self, value): - if not isinstance(value, (str, u''.__class__)): - raise TypeError('expected value to be a string') + if not isinstance(value, (str, u"".__class__)): + raise TypeError("expected value to be a string") # Specifier def align(self, value): self._validate_named(value, Align) - self._specifier['align'] = value + self._specifier["align"] = value return self def fill(self, value): self._validate_char(value) - self._specifier['fill'] = value + self._specifier["fill"] = value return self def group(self, value): @@ -158,7 +143,7 @@ def group(self, value): self._validate_named(value, Group) - self._specifier['group'] = value + self._specifier["group"] = value return self def padding(self, value): @@ -167,39 +152,37 @@ def padding(self, value): self._validate_named(value, Padding) - self._specifier['padding'] = value + self._specifier["padding"] = value return self def padding_width(self, value): self._validate_non_negative_integer_or_none(value) - self._specifier['width'] = value if value is not None else '' + self._specifier["width"] = value if value is not None else "" return self def precision(self, value): self._validate_non_negative_integer_or_none(value) - self._specifier['precision'] = ( - '.{0}'.format(value) if value is not None else '' - ) + self._specifier["precision"] = ".{0}".format(value) if value is not None else "" return self def scheme(self, value): self._validate_named(value, Scheme) - self._specifier['type'] = value + self._specifier["type"] = value return self def sign(self, value): self._validate_named(value, Sign) - self._specifier['sign'] = value + self._specifier["sign"] = value return self def symbol(self, value): self._validate_named(value, Symbol) - self._specifier['symbol'] = value + self._specifier["symbol"] = value return self def trim(self, value): @@ -208,66 +191,66 @@ def trim(self, value): self._validate_named(value, Trim) - self._specifier['trim'] = value + self._specifier["trim"] = value return self # Locale def symbol_prefix(self, value): self._validate_string(value) - if 'symbol' not in self._locale: - self._locale['symbol'] = [value, ''] + if "symbol" not in self._locale: + self._locale["symbol"] = [value, ""] else: - self._locale['symbol'][0] = value + self._locale["symbol"][0] = value return self def symbol_suffix(self, value): self._validate_string(value) - if 'symbol' not in self._locale: - self._locale['symbol'] = ['', value] + if "symbol" not in self._locale: + self._locale["symbol"] = ["", value] else: - self._locale['symbol'][1] = value + self._locale["symbol"][1] = value return self def decimal_delimiter(self, value): self._validate_char(value) - self._locale['decimal'] = value + self._locale["decimal"] = value return self def group_delimiter(self, value): self._validate_char(value) - self._locale['group'] = value + self._locale["group"] = value return self def groups(self, groups): groups = ( - groups if isinstance(groups, list) else - [groups] if isinstance(groups, int) else None + groups + if isinstance(groups, list) + else [groups] + if isinstance(groups, int) + else None ) if not isinstance(groups, list): - raise TypeError( - 'expected groups to be an integer or a list of integers' - ) + raise TypeError("expected groups to be an integer or a list of integers") if len(groups) == 0: raise ValueError( - 'expected groups to be an integer or a list of ' - 'one or more integers' + "expected groups to be an integer or a list of " "one or more integers" ) for group in groups: if not isinstance(group, int): - raise TypeError('expected entry to be an integer') + raise TypeError("expected entry to be an integer") if group <= 0: - raise ValueError('expected entry to be a non-negative integer') + raise ValueError("expected entry to be a non-negative integer") - self._locale['grouping'] = groups + self._locale["grouping"] = groups return self # Nully @@ -284,21 +267,21 @@ def si_prefix(self, value): def to_plotly_json(self): f = {} - f['locale'] = self._locale.copy() - f['nully'] = self._nully - f['prefix'] = self._prefix - aligned = self._specifier['align'] != Align.default - f['specifier'] = '{}{}{}{}{}{}{}{}{}{}'.format( - self._specifier['fill'] if aligned else '', - self._specifier['align'], - self._specifier['sign'], - self._specifier['symbol'], - self._specifier['padding'], - self._specifier['width'], - self._specifier['group'], - self._specifier['precision'], - self._specifier['trim'], - self._specifier['type'] + f["locale"] = self._locale.copy() + f["nully"] = self._nully + f["prefix"] = self._prefix + aligned = self._specifier["align"] != Align.default + f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format( + self._specifier["fill"] if aligned else "", + self._specifier["align"], + self._specifier["sign"], + self._specifier["symbol"], + self._specifier["padding"], + self._specifier["width"], + self._specifier["group"], + self._specifier["precision"], + self._specifier["trim"], + self._specifier["type"], ) return f diff --git a/dash_table_base/FormatTemplate.py b/dash_table_base/FormatTemplate.py index 9991437c7..9c2688ca8 100644 --- a/dash_table_base/FormatTemplate.py +++ b/dash_table_base/FormatTemplate.py @@ -7,16 +7,13 @@ def money(decimals, sign=Sign.default): precision=decimals, scheme=Scheme.fixed, sign=sign, - symbol=Symbol.yes + symbol=Symbol.yes, ) def percentage(decimals, rounded=False): if not isinstance(rounded, bool): - raise TypeError('expected rounded to be a boolean') + raise TypeError("expected rounded to be a boolean") rounded = Scheme.percentage_rounded if rounded else Scheme.percentage - return Format( - scheme=rounded, - precision=decimals - ) + return Format(scheme=rounded, precision=decimals) diff --git a/dash_table_base/__init__.py b/dash_table_base/__init__.py index 562a31187..b4d71488c 100644 --- a/dash_table_base/__init__.py +++ b/dash_table_base/__init__.py @@ -10,73 +10,82 @@ from ._imports_ import * from ._imports_ import __all__ -if not hasattr(_dash, 'development'): - print('Dash was not successfully imported. ' - 'Make sure you don\'t have a file ' - 'named \n"dash.py" in your current directory.', file=_sys.stderr) +if not hasattr(_dash, "development"): + print( + "Dash was not successfully imported. " + "Make sure you don't have a file " + 'named \n"dash.py" in your current directory.', + file=_sys.stderr, + ) _sys.exit(1) _basepath = _os.path.dirname(__file__) -_filepath = _os.path.abspath(_os.path.join(_basepath, 'package-info.json')) +_filepath = _os.path.abspath(_os.path.join(_basepath, "package-info.json")) with open(_filepath) as f: package = json.load(f) -package_name = package['name'].replace(' ', '_').replace('-', '_') -__version__ = package['version'] +package_name = package["name"].replace(" ", "_").replace("-", "_") +__version__ = package["version"] _current_path = _os.path.dirname(_os.path.abspath(__file__)) _this_module = _sys.modules[__name__] -async_resources = [ - 'export', - 'table', - 'highlight' -] +async_resources = ["export", "table", "highlight"] _js_dist = [] -_js_dist.extend([{ - 'relative_package_path': 'async-{}.js'.format(async_resource), - 'external_url': ( - 'https://unpkg.com/dash-table@{}' - '/dash_table/async-{}.js' - ).format(__version__, async_resource), - 'namespace': package_name, - 'async': True -} for async_resource in async_resources]) - -_js_dist.extend([{ - 'relative_package_path': 'async-{}.js.map'.format(async_resource), - 'external_url': ( - 'https://unpkg.com/dash-table@{}' - '/dash_table/async-{}.js.map' - ).format(__version__, async_resource), - 'namespace': package_name, - 'dynamic': True -} for async_resource in async_resources]) - -_js_dist.extend([ - { - 'relative_package_path': 'bundle.js', - 'external_url': ( - 'https://unpkg.com/dash-table@{}/dash_table/bundle.js' - ).format(__version__), - 'namespace': package_name - }, - { - 'relative_package_path': 'bundle.js.map', - 'external_url': ( - 'https://unpkg.com/dash-table@{}/dash_table/bundle.js.map' - ).format(__version__), - 'namespace': package_name, - 'dynamic': True - } -]) +_js_dist.extend( + [ + { + "relative_package_path": "async-{}.js".format(async_resource), + "external_url": ( + "https://unpkg.com/dash-table@{}" "/dash_table/async-{}.js" + ).format(__version__, async_resource), + "namespace": package_name, + "async": True, + } + for async_resource in async_resources + ] +) + +_js_dist.extend( + [ + { + "relative_package_path": "async-{}.js.map".format(async_resource), + "external_url": ( + "https://unpkg.com/dash-table@{}" "/dash_table/async-{}.js.map" + ).format(__version__, async_resource), + "namespace": package_name, + "dynamic": True, + } + for async_resource in async_resources + ] +) + +_js_dist.extend( + [ + { + "relative_package_path": "bundle.js", + "external_url": ( + "https://unpkg.com/dash-table@{}/dash_table/bundle.js" + ).format(__version__), + "namespace": package_name, + }, + { + "relative_package_path": "bundle.js.map", + "external_url": ( + "https://unpkg.com/dash-table@{}/dash_table/bundle.js.map" + ).format(__version__), + "namespace": package_name, + "dynamic": True, + }, + ] +) _css_dist = [] for _component in __all__: - setattr(locals()[_component], '_js_dist', _js_dist) - setattr(locals()[_component], '_css_dist', _css_dist) + setattr(locals()[_component], "_js_dist", _js_dist) + setattr(locals()[_component], "_css_dist", _css_dist) diff --git a/dev-requirements.txt b/dev-requirements.txt index 4cbe1f54e..3b2cba63d 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,4 @@ +black flake8 pandas preconditions diff --git a/index.py b/index.py index 552e10389..392dc8e62 100644 --- a/index.py +++ b/index.py @@ -19,7 +19,8 @@ apps = { filename.replace(".py", "").replace("app_", ""): getattr( getattr( - __import__(".".join(["tests", "integration", filename.replace(".py", "")])), "integration" + __import__(".".join(["tests", "integration", filename.replace(".py", "")])), + "integration", ), filename.replace(".py", ""), ) diff --git a/package.json b/package.json index 241070d4f..70bd6ab90 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "private::build:py": "dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json && cp dash_table_base/** dash_table/ && dash-generate-components src/dash-table/dash/DataTable.js dash_table -p package-info.json --r-prefix 'dash'", "private::host_js": "http-server ./dash_table -c-1 --silent", "private::lint:ts": "tslint --project tsconfig.json --config tslint.json", - "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table", + "private::lint:py": "flake8 --exclude=DataTable.py,__init__.py,_imports_.py dash_table && black --check .", "private::wait_js": "wait-on http://localhost:8080", "private::opentests": "cypress open", "private::test.python": "python -m unittest tests/unit/format_test.py", @@ -33,7 +33,7 @@ "build.watch": "webpack-dev-server --content-base dash_table --mode development --config webpack.dev.config.js", "build": "run-s private::build:js private::build:py", "postbuild": "es-check es5 dash_table/*.js", - "format": "run-s \"private::lint:ts -- --fix\"", + "format": "run-s \"private::lint:ts -- --fix\" && black .", "lint": "run-s private::lint:*", "test.server": "pytest tests/selenium", "test.standalone": "run-p --race private::host_js private::test.standalone", diff --git a/setup.py b/setup.py index 28cfc703f..c85e3db81 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup import json -with open('package.json') as f: +with open("package.json") as f: package = json.load(f) package_name = str(package["name"].replace(" ", "_").replace("-", "_")) @@ -9,10 +9,10 @@ setup( name=package_name, version=package["version"], - author=package['author'], + author=package["author"], packages=[package_name], include_package_data=True, - license=package['license'], - description=package['description'] if 'description' in package else package_name, - install_requires=[] + license=package["license"], + description=package["description"] if "description" in package else package_name, + install_requires=[], ) diff --git a/tests/integration/review_app/test_app_df_backend_paging.py b/tests/integration/review_app/test_app_df_backend_paging.py index bc40a6a85..bd6109eaa 100644 --- a/tests/integration/review_app/test_app_df_backend_paging.py +++ b/tests/integration/review_app/test_app_df_backend_paging.py @@ -29,11 +29,7 @@ def test_rapp001_df_backend_paging(dash_duo): df = pd.read_csv( os.path.realpath( os.path.join( - os.path.dirname(__file__), - "..", - "..", - "assets", - "gapminder.csv", + os.path.dirname(__file__), "..", "..", "assets", "gapminder.csv", ) ) ) @@ -41,8 +37,7 @@ def test_rapp001_df_backend_paging(dash_duo): df["index"] = range(1, len(df) + 1) app = dash.Dash( - __name__, - external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], + __name__, external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], ) app.config.suppress_callback_exceptions = True @@ -55,8 +50,7 @@ def section_title(title): dash_table.DataTable( id=IDS["table"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) + {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) ], page_current=0, page_size=PAGE_SIZE, @@ -83,8 +77,7 @@ def section_title(title): dash_table.DataTable( id=IDS["table-sorting"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) + {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) ], page_current=0, page_size=PAGE_SIZE, @@ -109,8 +102,7 @@ def section_title(title): dash_table.DataTable( id=IDS["table-multi-sorting"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) + {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) ], page_current=0, page_size=PAGE_SIZE, @@ -146,8 +138,7 @@ def section_title(title): dash_table.DataTable( id=IDS["table-filtering"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) + {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) ], page_current=0, page_size=PAGE_SIZE, @@ -155,14 +146,11 @@ def section_title(title): filter_action="custom", filter_query="", ), - section_title( - "Backend Paging with Filtering and Multi-Column Sorting" - ), + section_title("Backend Paging with Filtering and Multi-Column Sorting"), dash_table.DataTable( id=IDS["table-sorting-filtering"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) + {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) ], page_current=0, page_size=PAGE_SIZE, @@ -216,10 +204,7 @@ def section_title(title): @app.callback( Output(IDS["table"], "data"), - [ - Input(IDS["table"], "page_current"), - Input(IDS["table"], "page_size"), - ], + [Input(IDS["table"], "page_current"), Input(IDS["table"], "page_size"),], ) def update_graph(page_current, page_size): return df.iloc[ diff --git a/tests/integration/review_app/test_app_df_graph.py b/tests/integration/review_app/test_app_df_graph.py index 1dd3b0b4a..a7de35c40 100644 --- a/tests/integration/review_app/test_app_df_graph.py +++ b/tests/integration/review_app/test_app_df_graph.py @@ -16,11 +16,7 @@ def test_rapp002_df_graph(dash_duo): df = pd.read_csv( os.path.realpath( os.path.join( - os.path.dirname(__file__), - "..", - "..", - "assets", - "gapminder.csv", + os.path.dirname(__file__), "..", "..", "assets", "gapminder.csv", ) ) ) @@ -28,8 +24,7 @@ def test_rapp002_df_graph(dash_duo): df = df[df["year"] == 2007] app = dash.Dash( - __name__, - external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], + __name__, external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], ) app.layout = html.Div( [ @@ -37,8 +32,7 @@ def test_rapp002_df_graph(dash_duo): dash_table.DataTable( id=IDS["table"], columns=[ - {"name": i, "id": i, "deletable": True} - for i in df.columns + {"name": i, "id": i, "deletable": True} for i in df.columns ], data=df.to_dict("rows"), editable=True, diff --git a/tests/integration/test_table_export_csv.py b/tests/integration/test_table_export_csv.py index e91653eae..59e6bdaf6 100644 --- a/tests/integration/test_table_export_csv.py +++ b/tests/integration/test_table_export_csv.py @@ -17,7 +17,7 @@ def test_tbex001_table_export(dash_duo): export_format="csv", ) dash_duo.start_server(app) - dash_duo.wait_for_element('.export', timeout=1).click() + dash_duo.wait_for_element(".export", timeout=1).click() download = os.path.sep.join((dash_duo.download_path, "Data.csv")) wait.until(lambda: os.path.exists(download), timeout=2) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index edbab4704..3777e20df 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -8,7 +8,9 @@ from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait -_validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or (isinstance(col, int) and col >= 0) +_validate_col = lambda col: (isinstance(col, str) and len(col) > 0) or ( + isinstance(col, int) and col >= 0 +) _validate_col_id = lambda col_id: isinstance(col_id, str) and len(col_id) > 0 _validate_id = lambda id: isinstance(id, str) and len(id) > 0 _validate_key = lambda key: isinstance(key, str) and len(key) == 1 @@ -18,14 +20,15 @@ _validate_state = lambda state: state in [_READY, _LOADING, _ANY] _validate_target = lambda target: isinstance(target, DataTableFacade) -_READY = '.dash-spreadsheet:not(.dash-loading)' -_LOADING = '.dash-spreadsheet.dash-loading' -_ANY = '.dash-spreadsheet' +_READY = ".dash-spreadsheet:not(.dash-loading)" +_LOADING = ".dash-spreadsheet.dash-loading" +_ANY = ".dash-spreadsheet" _TIMEOUT = 10 + class DataTableContext: @preconditions(_validate_target) - def __init__ (self, target): + def __init__(self, target): self.target = target def __enter__(self): @@ -37,7 +40,7 @@ def __exit__(self, type, value, traceback): class HoldKeyContext: @preconditions(_validate_mixin, _validate_key) - def __init__ (self, mixin, key): + def __init__(self, mixin, key): self.mixin = mixin self.key = key @@ -55,53 +58,63 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_col, _validate_state) - def _get_cell_value(self, row, col, selector, state = _ANY): - return self.get(row, col, state).find_element_by_css_selector('.dash-cell-value') + def _get_cell_value(self, row, col, selector, state=_ANY): + return self.get(row, col, state).find_element_by_css_selector( + ".dash-cell-value" + ) @preconditions(_validate_row, _validate_col, _validate_state) - def click(self, row, col, state = _ANY): + def click(self, row, col, state=_ANY): return self.get(row, col, state).click() @preconditions(_validate_row, _validate_col, _validate_state) - def double_click(self, row, col, state = _ANY): + def double_click(self, row, col, state=_ANY): ac = ActionChains(self.mixin.driver) ac.move_to_element(self._get_cell_value(row, col, state)) - ac.pause(1) # sometimes experiencing incorrect behavior on scroll otherwise + ac.pause(1) # sometimes experiencing incorrect behavior on scroll otherwise ac.double_click() return ac.perform() @preconditions(_validate_row, _validate_col, _validate_state) - def get(self, row, col, state = _ANY): + def get(self, row, col, state=_ANY): self.mixin._wait_for_table(self.id, state) - return self.mixin.find_element( - '#{} {} tbody td.dash-cell.column-{}[data-dash-row="{}"]'.format(self.id, state, col, row) - ) if isinstance(col, int) else self.mixin.find_element( - '#{} {} tbody td.dash-cell[data-dash-column="{}"][data-dash-row="{}"]'.format(self.id, state, col, row) + return ( + self.mixin.find_element( + '#{} {} tbody td.dash-cell.column-{}[data-dash-row="{}"]'.format( + self.id, state, col, row + ) + ) + if isinstance(col, int) + else self.mixin.find_element( + '#{} {} tbody td.dash-cell[data-dash-column="{}"][data-dash-row="{}"]'.format( + self.id, state, col, row + ) + ) ) @preconditions(_validate_row, _validate_col, _validate_state) - def get_text(self, row, col, state = _ANY): + def get_text(self, row, col, state=_ANY): el = self._get_cell_value(row, col, state) - value = el.get_attribute('value') + value = el.get_attribute("value") return ( value - if value is not None and value != '' - else el.get_attribute('innerHTML') + if value is not None and value != "" + else el.get_attribute("innerHTML") ) @preconditions(_validate_row, _validate_col, _validate_state) - def is_active(self, row, col, state = _ANY): - input = self.get(row, col, state).find_element_by_css_selector('input') + def is_active(self, row, col, state=_ANY): + input = self.get(row, col, state).find_element_by_css_selector("input") - return 'focused' in input.get_attribute('class').split(' ') + return "focused" in input.get_attribute("class").split(" ") @preconditions(_validate_row, _validate_col, _validate_state) - def is_focused(self, row, col, state = _ANY): + def is_focused(self, row, col, state=_ANY): cell = self.get(row, col, state) - return 'focused' in cell.get_attribute('class').split(' ') + return "focused" in cell.get_attribute("class").split(" ") class DataTableColumnFacade(object): @@ -111,31 +124,36 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_col_id, _validate_state) - def get(self, row, col_id, state = _ANY): + def get(self, row, col_id, state=_ANY): self.mixin._wait_for_table(self.id, state) return self.mixin.find_elements( - '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format(self.id, state, col_id) + '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format( + self.id, state, col_id + ) )[row] @preconditions(_validate_row, _validate_col_id, _validate_state) - def hide(self, row, col_id, state = _ANY): + def hide(self, row, col_id, state=_ANY): self.get(row, col_id, state).find_element_by_css_selector( - '.column-header--hide' + ".column-header--hide" ).click() @preconditions(_validate_row, _validate_col_id, _validate_state) - def sort(self, row, col_id, state = _ANY): + def sort(self, row, col_id, state=_ANY): self.get(row, col_id, state).find_element_by_css_selector( - '.column-header--sort' + ".column-header--sort" ).click() @preconditions(_validate_col_id, _validate_state) - def filter(self, col_id, state = _ANY): + def filter(self, col_id, state=_ANY): return self.mixin.find_element( - '#{} {} tbody tr th.dash-filter[data-dash-column="{}"]'.format(self.id, state, col_id) + '#{} {} tbody tr th.dash-filter[data-dash-column="{}"]'.format( + self.id, state, col_id + ) ).click() + class DataTableRowFacade(object): @preconditions(_validate_id, _validate_mixin) def __init__(self, id, mixin): @@ -143,22 +161,26 @@ def __init__(self, id, mixin): self.mixin = mixin @preconditions(_validate_row, _validate_state) - def delete(self, row, state = _ANY): + def delete(self, row, state=_ANY): return self.mixin.find_elements( - '#{} {} tbody tr td.dash-delete-cell'.format(self.id, state) + "#{} {} tbody tr td.dash-delete-cell".format(self.id, state) )[row].click() @preconditions(_validate_row, _validate_state) - def select(self, row, state = _ANY): + def select(self, row, state=_ANY): return self.mixin.find_elements( - '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) + "#{} {} tbody tr td.dash-select-cell".format(self.id, state) )[row].click() @preconditions(_validate_row, _validate_state) - def is_selected(self, row, state = _ANY): - return self.mixin.find_elements( - '#{} {} tbody tr td.dash-select-cell'.format(self.id, state) - )[row].find_element_by_css_selector('input').is_selected() + def is_selected(self, row, state=_ANY): + return ( + self.mixin.find_elements( + "#{} {} tbody tr td.dash-select-cell".format(self.id, state) + )[row] + .find_element_by_css_selector("input") + .is_selected() + ) class DataTablePagingFacade(object): @@ -170,85 +192,69 @@ def __init__(self, id, mixin): def click_next_page(self): self.mixin._wait_for_table(self.id) - return self.mixin.find_element( - '#{} button.next-page'.format(self.id) - ).click() + return self.mixin.find_element("#{} button.next-page".format(self.id)).click() def click_prev_page(self): self.mixin._wait_for_table(self.id) return self.mixin.find_element( - '#{} button.previous-page'.format(self.id) + "#{} button.previous-page".format(self.id) ).click() def click_first_page(self): self.mixin._wait_for_table(self.id) - return self.mixin.find_element( - '#{} button.first-page'.format(self.id) - ).click() + return self.mixin.find_element("#{} button.first-page".format(self.id)).click() def click_last_page(self): self.mixin._wait_for_table(self.id) - return self.mixin.find_element( - '#{} button.last-page'.format(self.id) - ).click() + return self.mixin.find_element("#{} button.last-page".format(self.id)).click() def has_pagination(self): self.mixin._wait_for_table(self.id) - return len(self.mixin.find_elements('.previous-next-container')) != 0 + return len(self.mixin.find_elements(".previous-next-container")) != 0 def has_next_page(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element( - '#{} button.next-page'.format(self.id) - ) + el = self.mixin.find_element("#{} button.next-page".format(self.id)) return el is not None and el.is_enabled() def has_prev_page(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element( - '#{} button.previous-page'.format(self.id) - ) + el = self.mixin.find_element("#{} button.previous-page".format(self.id)) return el is not None and el.is_enabled() def has_first_page(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element( - '#{} button.first-page'.format(self.id) - ) + el = self.mixin.find_element("#{} button.first-page".format(self.id)) return el is not None and el.is_enabled() def has_last_page(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element( - '#{} button.last-page'.format(self.id) - ) + el = self.mixin.find_element("#{} button.last-page".format(self.id)) return el is not None and el.is_enabled() def click_current_page(self): self.mixin._wait_for_table(self.id) - return self.mixin.find_element( - '#{} input.current-page'.format(self.id) - ).click() + return self.mixin.find_element("#{} input.current-page".format(self.id)).click() def get_current_page(self): self.mixin._wait_for_table(self.id) return self.mixin.find_element( - '#{} input.current-page'.format(self.id) - ).get_attribute('placeholder') + "#{} input.current-page".format(self.id) + ).get_attribute("placeholder") class DataTableFacade(object): @@ -268,26 +274,27 @@ def is_ready(self): def is_loading(self): return self.mixin._wait_for_table(self.id, _LOADING) + class DataTableMixin(object): @preconditions(_validate_id, _validate_state) - def _wait_for_table(self, id, state = _ANY): + def _wait_for_table(self, id, state=_ANY): return WebDriverWait(self.driver, _TIMEOUT).until( - EC.presence_of_element_located((By.CSS_SELECTOR, '#{} {}'.format(id, state))) + EC.presence_of_element_located( + (By.CSS_SELECTOR, "#{} {}".format(id, state)) + ) ) @preconditions(_validate_id) def table(self, id): - return DataTableContext( - DataTableFacade(id, self) - ) + return DataTableContext(DataTableFacade(id, self)) def copy(self): - ActionChains(self.driver).key_down(Keys.CONTROL).send_keys('c').key_up( + ActionChains(self.driver).key_down(Keys.CONTROL).send_keys("c").key_up( Keys.CONTROL ).perform() def paste(self): - ActionChains(self.driver).key_down(Keys.CONTROL).send_keys('v').key_up( + ActionChains(self.driver).key_down(Keys.CONTROL).send_keys("v").key_up( Keys.CONTROL ).perform() @@ -296,9 +303,7 @@ def hold(self, key): return HoldKeyContext(self, key) def get_selected_text(self): - return self.driver.execute_script( - 'return window.getSelection().toString()' - ) + return self.driver.execute_script("return window.getSelection().toString()") @preconditions(_validate_keys) def send_keys(self, keys): @@ -338,4 +343,4 @@ def test(request, dash_thread_server, tmpdir): percy_finalize=request.config.getoption("nopercyfinalize"), percy_run=False, ) as dc: - yield dc \ No newline at end of file + yield dc diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index 8b72403bb..8c6cc912d 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -2,7 +2,6 @@ from dash.dependencies import Input, Output, State from dash.exceptions import PreventUpdate -import dash_core_components as dcc import dash_html_components as html from dash_table import DataTable @@ -13,7 +12,8 @@ url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) -df = rawDf.to_dict('rows') +df = rawDf.to_dict("rows") + def get_app(): app = dash.Dash(__name__) @@ -23,17 +23,22 @@ def get_app(): DataTable( id="table", data=df[0:250], - columns=[{"name": i, "id": i, "hideable": i == "Complaint ID"} for i in rawDf.columns], + columns=[ + {"name": i, "id": i, "hideable": i == "Complaint ID"} + for i in rawDf.columns + ], editable=True, - sort_action='native', + sort_action="native", include_headers_on_copy_paste=True, ), DataTable( id="table2", data=df[0:10], - columns=[{"name": i, "id": i, "deletable": True} for i in rawDf.columns], + columns=[ + {"name": i, "id": i, "deletable": True} for i in rawDf.columns + ], editable=True, - sort_action='native', + sort_action="native", include_headers_on_copy_paste=True, ), ] @@ -55,8 +60,8 @@ def update_data(timestamp, current, previous): for (i, datum) in enumerate(current): previous_datum = previous[i] - if datum['Unnamed: 0'] != previous_datum['Unnamed: 0']: - datum['Complaint ID'] = 'MODIFIED' + if datum["Unnamed: 0"] != previous_datum["Unnamed: 0"]: + datum["Complaint ID"] = "MODIFIED" modified = True if modified: @@ -70,48 +75,48 @@ def update_data(timestamp, current, previous): def test_tbcp001_copy_paste_callback(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 0) test.copy() target.cell.click(1, 0) test.paste() - assert target.cell.get_text(1, 0) == '0' - assert target.cell.get_text(1, 1) == 'MODIFIED' + assert target.cell.get_text(1, 0) == "0" + assert target.cell.get_text(1, 1) == "MODIFIED" def test_tbcp002_sorted_copy_paste_callback(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.column.sort(0, rawDf.columns[2]) - assert target.cell.get_text(0,0) == '11' + assert target.cell.get_text(0, 0) == "11" - target.cell.click(0,0) + target.cell.click(0, 0) test.copy() - target.cell.click(1,0) + target.cell.click(1, 0) test.paste() - assert target.cell.get_text(1,0) == '11' - assert target.cell.get_text(1,1) == 'MODIFIED' + assert target.cell.get_text(1, 0) == "11" + assert target.cell.get_text(1, 1) == "MODIFIED" - target.cell.click(1,1) + target.cell.click(1, 1) test.copy() - target.cell.click(2,1) + target.cell.click(2, 1) test.paste() - assert target.cell.get_text(1,0) == '11' - assert target.cell.get_text(2,1) == 'MODIFIED' + assert target.cell.get_text(1, 0) == "11" + assert target.cell.get_text(2, 1) == "MODIFIED" def test_tbcp003_copy_multiple_rows(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: with test.hold(Keys.SHIFT): target.cell.click(0, 0) target.cell.click(2, 0) @@ -122,13 +127,13 @@ def test_tbcp003_copy_multiple_rows(test): for i in range(3): assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) - assert target.cell.get_text(i + 3, 1) == 'MODIFIED' + assert target.cell.get_text(i + 3, 1) == "MODIFIED" def test_tbcp004_copy_9_and_10(test): test.start_server(get_app()) - with test.table('table') as source, test.table('table2') as target: + with test.table("table") as source, test.table("table2") as target: source.cell.click(9, 0) with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).perform() @@ -139,13 +144,15 @@ def test_tbcp004_copy_9_and_10(test): for row in range(2): for col in range(1): - assert target.cell.get_text(row, col) == source.cell.get_text(row + 9, col) + assert target.cell.get_text(row, col) == source.cell.get_text( + row + 9, col + ) def test_tbcp005_copy_multiple_rows_and_columns(test): test.start_server(get_app()) - with test.table('table') as table: + with test.table("table") as table: table.cell.click(0, 1) with test.hold(Keys.SHIFT): table.cell.click(2, 2) @@ -156,13 +163,15 @@ def test_tbcp005_copy_multiple_rows_and_columns(test): for row in range(3): for col in range(1, 3): - assert table.cell.get_text(row + 3, col) == table.cell.get_text(row, col) + assert table.cell.get_text(row + 3, col) == table.cell.get_text( + row, col + ) def test_tbcp006_copy_paste_between_tables(test): test.start_server(get_app()) - with test.table('table') as source, test.table('table2') as target: + with test.table("table") as source, test.table("table2") as target: source.cell.click(10, 0) with test.hold(Keys.SHIFT): source.cell.click(13, 3) @@ -173,14 +182,16 @@ def test_tbcp006_copy_paste_between_tables(test): for row in range(4): for col in range(4): - assert source.cell.get_text(row + 10, col) == target.cell.get_text(row, col) + assert source.cell.get_text(row + 10, col) == target.cell.get_text( + row, col + ) def test_tbcp007_copy_paste_with_hidden_column(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.hide(0, 'Complaint ID') + with test.table("table") as target: + target.column.hide(0, "Complaint ID") target.cell.click(0, 0) with test.hold(Keys.SHIFT): target.cell.click(2, 2) @@ -191,14 +202,16 @@ def test_tbcp007_copy_paste_with_hidden_column(test): for row in range(3): for col in range(3): - assert target.cell.get_text(row, col) == target.cell.get_text(row + 3, col + 1) + assert target.cell.get_text(row, col) == target.cell.get_text( + row + 3, col + 1 + ) def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.hide(0, 'Complaint ID') + with test.table("table") as target: + target.column.hide(0, "Complaint ID") target.cell.click(10, 0) with test.hold(Keys.SHIFT): target.cell.click(13, 2) @@ -209,4 +222,6 @@ def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): for row in range(4): for col in range(3): - assert target.cell.get_text(row + 10, col) == target.cell.get_text(row, col) \ No newline at end of file + assert target.cell.get_text(row + 10, col) == target.cell.get_text( + row, col + ) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 45affe06a..7b29fbf3e 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -1,9 +1,5 @@ import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate -import dash_core_components as dcc -import dash_html_components as html from dash_table import DataTable from selenium.webdriver.common.keys import Keys @@ -13,7 +9,8 @@ url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) -df = rawDf.to_dict('rows') +df = rawDf.to_dict("rows") + def get_app(): app = dash.Dash(__name__) @@ -24,8 +21,8 @@ def get_app(): data=df, editable=True, filter_action="native", - fixed_columns={ 'headers': True }, - fixed_rows={ 'headers': True }, + fixed_columns={"headers": True}, + fixed_rows={"headers": True}, page_action="native", row_deletable=True, row_selectable=True, @@ -38,16 +35,16 @@ def get_app(): def test_tbst001_get_cell(test): test.start_server(get_app()) - with test.table('table') as target: - assert target.cell.get_text(0, 0) == '0' + with test.table("table") as target: + assert target.cell.get_text(0, 0) == "0" target.paging.click_next_page() - assert target.cell.get_text(0, 0) == '250' + assert target.cell.get_text(0, 0) == "250" def test_tbst002_select_all_text(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 1) assert target.cell.get_text(0, 1) == test.get_selected_text() @@ -57,44 +54,44 @@ def test_tbst002_select_all_text(test): def test_tbst003_edit_on_enter(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc' + Keys.ENTER) + test.send_keys("abc" + Keys.ENTER) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" # https://github.com/plotly/dash-table/issues/107 def test_tbst004_edit_on_tab(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc' + Keys.TAB) + test.send_keys("abc" + Keys.TAB) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" def test_tbst005_edit_last_row_on_click_outside(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc') + test.send_keys("abc") target.cell.click(248, 0) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" # https://github.com/plotly/dash-table/issues/141 def test_tbst006_focused_arrow_left(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 1) - test.send_keys('abc' + Keys.LEFT) + test.send_keys("abc" + Keys.LEFT) - assert target.cell.get_text(249, 1) == 'abc' + assert target.cell.get_text(249, 1) == "abc" assert target.cell.is_focused(249, 0) @@ -102,11 +99,11 @@ def test_tbst006_focused_arrow_left(test): def test_tbst007_active_focused_arrow_right(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc' + Keys.RIGHT) + test.send_keys("abc" + Keys.RIGHT) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" assert target.cell.is_focused(249, 1) @@ -114,11 +111,11 @@ def test_tbst007_active_focused_arrow_right(test): def test_tbst008_active_focused_arrow_up(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc' + Keys.UP) + test.send_keys("abc" + Keys.UP) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" assert target.cell.is_focused(248, 0) @@ -126,18 +123,18 @@ def test_tbst008_active_focused_arrow_up(test): def test_tbst009_active_focused_arrow_down(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(249, 0) - test.send_keys('abc' + Keys.DOWN) + test.send_keys("abc" + Keys.DOWN) - assert target.cell.get_text(249, 0) == 'abc' + assert target.cell.get_text(249, 0) == "abc" assert target.cell.is_focused(249, 0) def test_tbst010_active_with_dblclick(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.double_click(0, 0) assert target.cell.is_active(0, 0) assert target.cell.get_text(0, 0) == test.get_selected_text() @@ -146,8 +143,7 @@ def test_tbst010_active_with_dblclick(test): def test_tbst011_delete_row(test): test.start_server(get_app()) - with test.table('table') as target: - text00 = target.cell.get_text(0, 0) + with test.table("table") as target: text01 = target.cell.get_text(1, 0) target.row.delete(0) @@ -157,11 +153,10 @@ def test_tbst011_delete_row(test): def test_tbst012_delete_sorted_row(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - text00 = target.cell.get_text(0, 0) text01 = target.cell.get_text(1, 0) target.row.delete(0) @@ -171,7 +166,7 @@ def test_tbst012_delete_sorted_row(test): def test_tbst013_select_row(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.row.select(0) assert target.row.is_selected(0) @@ -180,9 +175,9 @@ def test_tbst013_select_row(test): def test_tbst014_selected_sorted_row(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC target.row.select(0) assert target.row.is_selected(0) @@ -191,17 +186,17 @@ def test_tbst014_selected_sorted_row(test): def test_tbst015_selected_row_respects_sort(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.row.select(0) assert target.row.is_selected(0) - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC assert not target.row.is_selected(0) - target.column.sort(0, rawDf.columns[0]) # DESC -> None + target.column.sort(0, rawDf.columns[0]) # DESC -> None assert target.row.is_selected(0) @@ -209,12 +204,12 @@ def test_tbst015_selected_row_respects_sort(test): def test_tbst016_delete_cell(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 1) test.send_keys(Keys.BACKSPACE) test.send_keys(Keys.ENTER) - assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == "" # # https://github.com/plotly/dash-table/issues/700 @@ -231,16 +226,20 @@ def test_tbst016_delete_cell(test): def test_tbst018_delete_multiple_cells(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 1) with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( + Keys.RIGHT + ).perform() - ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys( + Keys.ENTER + ).perform() for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == '' + assert target.cell.get_text(row, col) == "" # # https://github.com/plotly/dash-table/issues/700 @@ -262,15 +261,15 @@ def test_tbst018_delete_multiple_cells(test): def test_tbst020_sorted_table_delete_cell(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC target.cell.click(0, 1) test.send_keys(Keys.BACKSPACE) test.send_keys(Keys.ENTER) - assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == "" # # https://github.com/plotly/dash-table/issues/700 @@ -290,19 +289,24 @@ def test_tbst020_sorted_table_delete_cell(test): def test_tbst022_sorted_table_delete_multiple_cells(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC target.cell.click(0, 1) with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( + Keys.RIGHT + ).perform() - ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys( + Keys.ENTER + ).perform() for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == '' + assert target.cell.get_text(row, col) == "" + # # https://github.com/plotly/dash-table/issues/700 # def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): @@ -320,4 +324,4 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): # for row in range(2): # for col in range(1, 3): -# assert target.cell.get_text(row, col) == '' \ No newline at end of file +# assert target.cell.get_text(row, col) == '' diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py index 0baa6d38a..9822dae76 100644 --- a/tests/selenium/test_derived_props.py +++ b/tests/selenium/test_derived_props.py @@ -1,76 +1,80 @@ import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate +from dash.dependencies import Input, Output -import dash_core_components as dcc import dash_html_components as html from dash_table import DataTable from selenium.webdriver.common.keys import Keys -from selenium.webdriver.common.action_chains import ActionChains import json import pandas as pd -import time url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url, nrows=100) -rawDf['id'] = rawDf.index + 3000 +rawDf["id"] = rawDf.index + 3000 -df = rawDf.to_dict('rows') +df = rawDf.to_dict("rows") props = [ - 'active_cell', - 'start_cell', - 'end_cell', - 'selected_cells', - 'selected_rows', - 'selected_row_ids', - 'derived_viewport_selected_rows', - 'derived_viewport_selected_row_ids', - 'derived_virtual_selected_rows', - 'derived_virtual_selected_row_ids', - 'derived_viewport_indices', - 'derived_viewport_row_ids', - 'derived_virtual_indices', - 'derived_virtual_row_ids' + "active_cell", + "start_cell", + "end_cell", + "selected_cells", + "selected_rows", + "selected_row_ids", + "derived_viewport_selected_rows", + "derived_viewport_selected_row_ids", + "derived_virtual_selected_rows", + "derived_virtual_selected_row_ids", + "derived_viewport_indices", + "derived_viewport_row_ids", + "derived_virtual_indices", + "derived_virtual_row_ids", ] + def get_app(): app = dash.Dash(__name__) - app.layout = html.Div([ - DataTable( - id="table", - columns=[{"name": i, "id": i} for i in rawDf.columns], - data=df, - editable=True, - filter_action="native", - fixed_columns={ 'headers': True }, - fixed_rows={ 'headers': True }, - page_action="native", - page_size=10, - row_deletable=True, - row_selectable=True, - sort_action="native", - ), - html.Div(id='props_container', children=['Nothing yet']) - ]) + app.layout = html.Div( + [ + DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=df, + editable=True, + filter_action="native", + fixed_columns={"headers": True}, + fixed_rows={"headers": True}, + page_action="native", + page_size=10, + row_deletable=True, + row_selectable=True, + sort_action="native", + ), + html.Div(id="props_container", children=["Nothing yet"]), + ] + ) @app.callback( - Output("props_container", "children"), - [Input("table", prop) for prop in props] + Output("props_container", "children"), [Input("table", prop) for prop in props], ) def show_props(*args): # return 'Something yet!' # print('show props') - return html.Table([ - html.Tr([ - html.Td(prop), - html.Td(json.dumps(val) if val is not None else 'None', id=prop) - ]) - for prop, val in zip(props, args) - ]) + return html.Table( + [ + html.Tr( + [ + html.Td(prop), + html.Td( + json.dumps(val) if val is not None else "None", id=prop, + ), + ] + ) + for prop, val in zip(props, args) + ] + ) return app @@ -78,58 +82,120 @@ def show_props(*args): def test_tdrp001_select_rows(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.row.select(0) target.row.select(1) - assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(2))) - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3002))) - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(2))) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(2))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(2))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp002_select_cell(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 0) active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) - assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps([active]) - assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + assert test.find_element("#active_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#start_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps([active]) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp003_select_cells(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 0) with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) @@ -139,24 +205,61 @@ def test_tdrp003_select_cells(test): selected = [] for row in range(3): for col in range(3): - selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) - - assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) - assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) - assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) - assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + selected.append( + dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, + ) + ) + + assert test.find_element("#active_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#start_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[0]) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + selected[-1] + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) # reduce selection with test.hold(Keys.SHIFT): @@ -165,30 +268,67 @@ def test_tdrp003_select_cells(test): selected = [] for row in range(2): for col in range(2): - selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) - - assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) - assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) - assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) - assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + selected.append( + dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, + ) + ) + + assert test.find_element("#active_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#start_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[0]) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + selected[-1] + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp004_navigate_selected_cells(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 0) with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) @@ -196,28 +336,68 @@ def test_tdrp004_navigate_selected_cells(test): selected = [] for row in range(3): for col in range(3): - selected.append(dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000)) + selected.append( + dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, + ) + ) for row in range(3): for col in range(3): - active = dict(row=row, column=col, column_id=rawDf.columns[col], row_id=row+3000) - - assert test.find_element('#active_cell').get_attribute('innerHTML') == json.dumps(active) - assert test.find_element('#start_cell').get_attribute('innerHTML') == json.dumps(selected[0]) - assert test.find_element('#end_cell').get_attribute('innerHTML') == json.dumps(selected[-1]) - assert test.find_element('#selected_cells').get_attribute('innerHTML') == json.dumps(selected) - assert test.find_element('#selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + active = dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, + ) + + assert test.find_element("#active_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#start_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[0]) + assert test.find_element("#end_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[-1]) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + + assert test.find_element( + "#derived_viewport_selected_rows" + ).get_attribute("innerHTML") in ["None", json.dumps([])] + assert test.find_element( + "#derived_viewport_selected_row_ids" + ).get_attribute("innerHTML") in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element( + "#derived_virtual_selected_rows" + ).get_attribute("innerHTML") in ["None", json.dumps([])] + assert test.find_element( + "#derived_virtual_selected_row_ids" + ).get_attribute("innerHTML") in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) test.send_keys(Keys.TAB) @@ -225,64 +405,160 @@ def test_tdrp004_navigate_selected_cells(test): def test_tdrp005_filtered_and_sorted_row_select(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.row.select(0) target.row.select(1) target.row.select(2) - assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(10))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3010))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(100))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100))) + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) target.column.filter(rawDf.columns[0]) - test.send_keys('is even' + Keys.ENTER) - - assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(0, 2))) - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 20, 2))) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3020, 2))) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(0, 2))) - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 100, 2))) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100, 2))) - - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - - assert test.find_element('#active_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#start_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#end_cell').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_cells').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#selected_rows').get_attribute('innerHTML') == json.dumps(list(range(3))) - assert test.find_element('#selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003))) - - assert test.find_element('#derived_viewport_selected_rows').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_selected_row_ids').get_attribute('innerHTML') in ['None', json.dumps([])] - assert test.find_element('#derived_viewport_indices').get_attribute('innerHTML') == json.dumps(list(range(80, 100, 2))[::-1]) - assert test.find_element('#derived_viewport_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3080, 3100, 2))[::-1]) - - assert test.find_element('#derived_virtual_selected_rows').get_attribute('innerHTML') == json.dumps(list(range(48, 50))[::-1]) - assert test.find_element('#derived_virtual_selected_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element('#derived_virtual_indices').get_attribute('innerHTML') == json.dumps(list(range(0, 100, 2))[::-1]) - assert test.find_element('#derived_virtual_row_ids').get_attribute('innerHTML') == json.dumps(list(range(3000, 3100, 2))[::-1]) + test.send_keys("is even" + Keys.ENTER) + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 2))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 20, 2))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3020, 2))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 2))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 100, 2))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100, 2))) + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(80, 100, 2))[::-1]) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3080, 3100, 2))[::-1]) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(48, 50))[::-1]) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 100, 2))[::-1]) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100, 2))[::-1]) diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py index 6e7088090..dd89ca967 100644 --- a/tests/selenium/test_editable.py +++ b/tests/selenium/test_editable.py @@ -1,5 +1,5 @@ import dash -from dash.dependencies import Input, Output, State +from dash.dependencies import Input, Output from dash.exceptions import PreventUpdate import dash_core_components as dcc @@ -8,42 +8,43 @@ from multiprocessing import Lock from selenium.webdriver.common.keys import Keys -from selenium.webdriver.common.action_chains import ActionChains import pandas as pd url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) -df = rawDf.to_dict('rows') +df = rawDf.to_dict("rows") + def get_app_and_locks(): app = dash.Dash(__name__) - app.layout = html.Div([ - dcc.Input(id='input'), - html.Button(['Blocking'], id='blocking'), - html.Button(['Non Blocking'], id='non-blocking'), - DataTable( - id="table", - columns=[{"name": i, "id": i} for i in rawDf.columns], - data=df, - editable=True, - filter_action="native", - fixed_columns={ 'headers': True }, - fixed_rows={ 'headers': True }, - page_action="native", - row_deletable=True, - row_selectable=True, - sort_action="native", - ) - ]) + app.layout = html.Div( + [ + dcc.Input(id="input"), + html.Button(["Blocking"], id="blocking"), + html.Button(["Non Blocking"], id="non-blocking"), + DataTable( + id="table", + columns=[{"name": i, "id": i} for i in rawDf.columns], + data=df, + editable=True, + filter_action="native", + fixed_columns={"headers": True}, + fixed_rows={"headers": True}, + page_action="native", + row_deletable=True, + row_selectable=True, + sort_action="native", + ), + ] + ) blocking_lock = Lock() non_blocking_lock = Lock() @app.callback( - Output("table", "style_cell_conditional"), - [Input("non-blocking", "n_clicks")] + Output("table", "style_cell_conditional"), [Input("non-blocking", "n_clicks")], ) def non_blocking_callback(clicks): if clicks is None: @@ -52,10 +53,7 @@ def non_blocking_callback(clicks): with non_blocking_lock: return [] - @app.callback( - Output("table", "data"), - [Input("blocking", "n_clicks")] - ) + @app.callback(Output("table", "data"), [Input("blocking", "n_clicks")]) def blocking_callback(clicks): if clicks is None: raise PreventUpdate @@ -71,15 +69,17 @@ def test_tedi001_loading_on_data_change(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with blocking: - test.find_element('#blocking').click() + test.find_element("#blocking").click() target.is_loading() target.cell.click(0, 0) - assert len(target.cell.get(0, 0).find_elements_by_css_selector('input')) == 0 + assert ( + len(target.cell.get(0, 0).find_elements_by_css_selector("input")) == 0 + ) target.is_ready() - assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None def test_tedi002_ready_on_non_data_change(test): @@ -87,15 +87,17 @@ def test_tedi002_ready_on_non_data_change(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with blocking: - test.find_element('#non-blocking').click() + test.find_element("#non-blocking").click() target.is_ready() target.cell.click(0, 0) - assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + assert ( + target.cell.get(0, 0).find_element_by_css_selector("input") is not None + ) target.is_ready() - assert target.cell.get(0, 0).find_element_by_css_selector('input') is not None + assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None def test_tedi003_does_not_steal_focus(test): @@ -103,14 +105,14 @@ def test_tedi003_does_not_steal_focus(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with blocking: - test.find_element('#blocking').click() - test.find_element('#input').click() - assert test.find_element('#input') == test.driver.switch_to.active_element + test.find_element("#blocking").click() + test.find_element("#input").click() + assert test.find_element("#input") == test.driver.switch_to.active_element target.is_ready() - assert test.find_element('#input') == test.driver.switch_to.active_element + assert test.find_element("#input") == test.driver.switch_to.active_element def test_tedi004_edit_on_non_blocking(test): @@ -118,12 +120,12 @@ def test_tedi004_edit_on_non_blocking(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with blocking: - test.find_element('#non-blocking').click() + test.find_element("#non-blocking").click() target.cell.click(0, 0) - test.send_keys('abc' + Keys.ENTER) - assert target.cell.get_text(0, 0) == 'abc' + test.send_keys("abc" + Keys.ENTER) + assert target.cell.get_text(0, 0) == "abc" def test_tedi005_prevent_copy_paste_on_blocking(test): @@ -131,9 +133,9 @@ def test_tedi005_prevent_copy_paste_on_blocking(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with blocking: - test.find_element('#blocking').click() + test.find_element("#blocking").click() target.cell.click(0, 0) with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.RIGHT) @@ -144,7 +146,9 @@ def test_tedi005_prevent_copy_paste_on_blocking(test): for row in range(2): for col in range(2): - assert target.cell.get_text(row + 2, col) != target.cell.get_text(row, col) + assert target.cell.get_text(row + 2, col) != target.cell.get_text( + row, col + ) def test_tedi006_allow_copy_paste_on_non_blocking(test): @@ -152,9 +156,9 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): test.start_server(app) - with test.table('table') as target: + with test.table("table") as target: with non_blocking: - test.find_element('#non-blocking').click() + test.find_element("#non-blocking").click() target.cell.click(0, 0) with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.RIGHT) @@ -165,4 +169,6 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): for row in range(2): for col in range(2): - assert target.cell.get_text(row + 2, col) == target.cell.get_text(row, col) + assert target.cell.get_text(row + 2, col) == target.cell.get_text( + row, col + ) diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index f62c98ad5..9536118e6 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -1,24 +1,19 @@ import dash -from dash.dependencies import Input, Output, State -from dash.exceptions import PreventUpdate - -import dash_core_components as dcc -import dash_html_components as html from dash_table import DataTable -from selenium.webdriver.common.keys import Keys -from selenium.webdriver.common.action_chains import ActionChains - import pandas as pd url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) -rawDf['Complaint ID'] = rawDf['Complaint ID'].map(lambda x: '**' + str(x) + '**') -rawDf['Product'] = rawDf['Product'].map(lambda x: '[' + str(x) + '](plot.ly)') -rawDf['Issue'] = rawDf['Issue'].map(lambda x: '![' + str(x) + '](https://dash.plot.ly/assets/images/logo.png)') -rawDf['State'] = rawDf['State'].map(lambda x: '```python\n"{}"\n```'.format(x)) +rawDf["Complaint ID"] = rawDf["Complaint ID"].map(lambda x: "**" + str(x) + "**") +rawDf["Product"] = rawDf["Product"].map(lambda x: "[" + str(x) + "](plot.ly)") +rawDf["Issue"] = rawDf["Issue"].map( + lambda x: "![" + str(x) + "](https://dash.plot.ly/assets/images/logo.png)" +) +rawDf["State"] = rawDf["State"].map(lambda x: '```python\n"{}"\n```'.format(x)) + +df = rawDf.to_dict("rows") -df = rawDf.to_dict('rows') def get_app(): app = dash.Dash(__name__) @@ -27,17 +22,17 @@ def get_app(): id="table", data=df[0:250], columns=[ - {"id": "Complaint ID", "name": "Complaint ID", "presentation": "markdown"}, + {"id": "Complaint ID", "name": "Complaint ID", "presentation": "markdown",}, {"id": "Product", "name": "Product", "presentation": "markdown"}, {"id": "Sub-product", "name": "Sub-product"}, {"id": "Issue", "name": "Issue", "presentation": "markdown"}, {"id": "Sub-issue", "name": "Sub-issue"}, {"id": "State", "name": "State", "presentation": "markdown"}, - {"id": "ZIP", "name": "ZIP"} + {"id": "ZIP", "name": "ZIP"}, ], editable=True, - sort_action='native', - include_headers_on_copy_paste=True + sort_action="native", + include_headers_on_copy_paste=True, ) return app @@ -46,50 +41,59 @@ def get_app(): def test_tmcp001_copy_markdown_to_text(test): test.start_server(get_app()) - with test.table('table') as target: - target.cell.click(0, 'Issue') + with test.table("table") as target: + target.cell.click(0, "Issue") test.copy() - target.cell.click(0, 'Sub-product') + target.cell.click(0, "Sub-product") test.paste() - assert target.cell.get_text(0, 2) == df[0].get('Issue') + assert target.cell.get_text(0, 2) == df[0].get("Issue") def test_tmcp002_copy_markdown_to_markdown(test): test.start_server(get_app()) - with test.table('table') as target: - target.cell.click(0, 'Product') + with test.table("table") as target: + target.cell.click(0, "Product") test.copy() - target.cell.click(0, 'Complaint ID') + target.cell.click(0, "Complaint ID") test.paste() - assert target.cell.get_text(0, 'Complaint ID') == target.cell.get_text(0, 'Product') + assert target.cell.get_text(0, "Complaint ID") == target.cell.get_text( + 0, "Product" + ) def test_tmcp003_copy_text_to_markdown(test): test.start_server(get_app()) - with test.table('table') as target: - target.cell.click(1, 'Sub-product') + with test.table("table") as target: + target.cell.click(1, "Sub-product") test.copy() - target.cell.click(1, 'Product') + target.cell.click(1, "Product") test.paste() - assert target.cell.get(1, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == df[1].get('Sub-product') + assert target.cell.get(1, "Product").find_element_by_css_selector( + ".dash-cell-value > p" + ).get_attribute("innerHTML") == df[1].get("Sub-product") def test_tmcp004_copy_null_text_to_markdown(test): test.start_server(get_app()) - with test.table('table') as target: - target.cell.click(0, 'Sub-product') + with test.table("table") as target: + target.cell.click(0, "Sub-product") test.copy() - target.cell.click(0, 'Product') + target.cell.click(0, "Product") test.paste() - assert target.cell.get(0, 'Product').find_element_by_css_selector('.dash-cell-value > p').get_attribute('innerHTML') == 'null' \ No newline at end of file + assert ( + target.cell.get(0, "Product") + .find_element_by_css_selector(".dash-cell-value > p") + .get_attribute("innerHTML") + == "null" + ) diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index fd8a4aecf..7903101c2 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -15,11 +15,12 @@ url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) -df = rawDf.to_dict('rows') +df = rawDf.to_dict("rows") PAGE_SIZE = 5 pages = math.ceil(len(df) / PAGE_SIZE) + def get_app(mode, data=df, page_count=None): app = dash.Dash(__name__) @@ -29,10 +30,10 @@ def get_app(mode, data=df, page_count=None): app.layout = DataTable( id="table", columns=[{"name": i, "id": i} for i in rawDf.columns], - data=data if mode == 'native' else data[0 : PAGE_SIZE], + data=data if mode == "native" else data[0:PAGE_SIZE], editable=True, - fixed_columns={ 'headers': True }, - fixed_rows={ 'headers': True }, + fixed_columns={"headers": True}, + fixed_rows={"headers": True}, page_action=mode, page_count=page_count, page_size=PAGE_SIZE, @@ -40,61 +41,60 @@ def get_app(mode, data=df, page_count=None): row_selectable=True, ) - if mode == 'custom': + if mode == "custom": + @app.callback( - [Output('table', 'data')], - [Input('table', 'page_current'), Input('table', 'page_size')] + [Output("table", "data")], + [Input("table", "page_current"), Input("table", "page_size")], ) def update_table(page_current, page_size): if page_current is None or page_size is None: raise PreventUpdate - return data[ - page_current * page_size: (page_current + 1) * page_size - ], + return (data[page_current * page_size : (page_current + 1) * page_size],) return app -@pytest.mark.parametrize('mode', ['custom', 'native']) +@pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag001_next_previous(test, mode): test.start_server(get_app(mode)) - with test.table('table') as target: - assert target.cell.get_text(0, 0) == '0' + with test.table("table") as target: + assert target.cell.get_text(0, 0) == "0" assert target.paging.has_next_page() assert not target.paging.has_prev_page() target.paging.click_next_page() - assert target.cell.get_text(0, 0) == '5' + assert target.cell.get_text(0, 0) == "5" assert target.paging.has_next_page() assert target.paging.has_prev_page() target.paging.click_prev_page() - assert target.cell.get_text(0, 0) == '0' + assert target.cell.get_text(0, 0) == "0" assert target.paging.has_next_page() assert not target.paging.has_prev_page() -@pytest.mark.parametrize('mode', ['custom', 'native']) +@pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag002_ops_on_first_page(test, mode): test.start_server(get_app(mode)) - with test.table('table') as target: - assert target.paging.get_current_page() == '1' + with test.table("table") as target: + assert target.paging.get_current_page() == "1" assert not target.paging.has_first_page() assert not target.paging.has_prev_page() assert target.paging.has_next_page() assert target.paging.has_last_page() -@pytest.mark.parametrize('mode', ['custom', 'native']) +@pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag003_ops_on_last_page(test, mode): test.start_server(get_app(mode)) - with test.table('table') as target: + with test.table("table") as target: target.paging.click_last_page() assert target.paging.get_current_page() == str(pages) @@ -105,49 +105,46 @@ def test_tpag003_ops_on_last_page(test, mode): def test_tpag004_ops_input_with_enter(test): - test.start_server(get_app('native')) + test.start_server(get_app("native")) - with test.table('table') as target: + with test.table("table") as target: text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == '1' + assert target.paging.get_current_page() == "1" target.paging.click_current_page() - test.send_keys('100' + Keys.ENTER) + test.send_keys("100" + Keys.ENTER) - assert target.paging.get_current_page() == '100' + assert target.paging.get_current_page() == "100" assert target.cell.get_text(0, 0) != text00 def test_tpag005_ops_input_with_unfocus(test): - test.start_server(get_app('native')) + test.start_server(get_app("native")) - with test.table('table') as target: + with test.table("table") as target: text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == '1' + assert target.paging.get_current_page() == "1" target.paging.click_current_page() - test.send_keys('100') + test.send_keys("100") target.cell.click(0, 0) - assert target.paging.get_current_page() == '100' + assert target.paging.get_current_page() == "100" assert target.cell.get_text(0, 0) != text00 -@pytest.mark.parametrize('value,expected_value', [ - (0, 1), - (-1, 1), - ('a', 1), - (pages * 2, pages) -]) +@pytest.mark.parametrize( + "value,expected_value", [(0, 1), (-1, 1), ("a", 1), (pages * 2, pages)] +) def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): - test.start_server(get_app('native')) + test.start_server(get_app("native")) - with test.table('table') as target: + with test.table("table") as target: text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == '1' + assert target.paging.get_current_page() == "1" target.paging.click_current_page() test.send_keys(str(value) + Keys.ENTER) @@ -155,19 +152,16 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): assert target.paging.get_current_page() == str(expected_value) -@pytest.mark.parametrize('value,expected_value', [ - (0, 1), - (-1, 1), - ('a', 1), - (pages * 2, pages) -]) +@pytest.mark.parametrize( + "value,expected_value", [(0, 1), (-1, 1), ("a", 1), (pages * 2, pages)] +) def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): - test.start_server(get_app('native')) + test.start_server(get_app("native")) - with test.table('table') as target: + with test.table("table") as target: text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == '1' + assert target.paging.get_current_page() == "1" target.paging.click_current_page() test.send_keys(str(value)) @@ -176,25 +170,25 @@ def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): assert target.paging.get_current_page() == str(expected_value) -@pytest.mark.parametrize('mode', ['custom', 'native']) +@pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag008_hide_with_single_page(test, mode): - test.start_server(get_app(mode=mode, data=df[0: PAGE_SIZE])) + test.start_server(get_app(mode=mode, data=df[0:PAGE_SIZE])) - with test.table('table') as target: + with test.table("table") as target: assert not target.paging.has_pagination() def test_tpag009_hide_with_invalid_page_count(test): - test.start_server(get_app(mode='custom', page_count=-1)) + test.start_server(get_app(mode="custom", page_count=-1)) - with test.table('table') as target: + with test.table("table") as target: assert not target.paging.has_pagination() def test_tpag010_limits_page(test): - test.start_server(get_app(mode='custom', page_count=10)) + test.start_server(get_app(mode="custom", page_count=10)) - with test.table('table') as target: + with test.table("table") as target: target.paging.click_last_page() - assert target.paging.get_current_page() == '10' \ No newline at end of file + assert target.paging.get_current_page() == "10" diff --git a/tests/unit/format_test.py b/tests/unit/format_test.py index a31b0e78c..63bc5cd35 100644 --- a/tests/unit/format_test.py +++ b/tests/unit/format_test.py @@ -4,70 +4,89 @@ from dash_table.Format import Format import dash_table.FormatTemplate as FormatTemplate + class FormatTest(unittest.TestCase): def validate_complex(self, res): - self.assertEqual(res['locale']['symbol'][0], 'a') - self.assertEqual(res['locale']['symbol'][1], 'bc') - self.assertEqual(res['locale']['decimal'], 'x') - self.assertEqual(res['locale']['group'], 'y') - self.assertEqual(res['nully'], 'N/A') - self.assertEqual(res['prefix'], None) - self.assertEqual(res['specifier'], '.^($010,.6s') + self.assertEqual(res["locale"]["symbol"][0], "a") + self.assertEqual(res["locale"]["symbol"][1], "bc") + self.assertEqual(res["locale"]["decimal"], "x") + self.assertEqual(res["locale"]["group"], "y") + self.assertEqual(res["nully"], "N/A") + self.assertEqual(res["prefix"], None) + self.assertEqual(res["specifier"], ".^($010,.6s") def test_complex_and_valid_in_ctor(self): res = Format( align=f.Align.center, - fill='.', + fill=".", group=f.Group.yes, padding=True, padding_width=10, precision=6, - scheme='s', + scheme="s", sign=f.Sign.parantheses, symbol=f.Symbol.yes, - symbol_prefix='a', - symbol_suffix='bc', - decimal_delimiter='x', - group_delimiter='y', + symbol_prefix="a", + symbol_suffix="bc", + decimal_delimiter="x", + group_delimiter="y", groups=[2, 2, 2, 3], - nully='N/A', - si_prefix=f.Prefix.none + nully="N/A", + si_prefix=f.Prefix.none, ) self.validate_complex(res.to_plotly_json()) def test_complex_and_valid_in_fluent(self): - res = Format().align(f.Align.center).fill('.').group(f.Group.yes).padding(True).padding_width(10).precision(6).scheme('s').sign(f.Sign.parantheses).symbol(f.Symbol.yes).symbol_prefix('a').symbol_suffix('bc').decimal_delimiter('x').group_delimiter('y').groups([2, 2, 2, 3]).nully('N/A').si_prefix(f.Prefix.none) + res = ( + Format() + .align(f.Align.center) + .fill(".") + .group(f.Group.yes) + .padding(True) + .padding_width(10) + .precision(6) + .scheme("s") + .sign(f.Sign.parantheses) + .symbol(f.Symbol.yes) + .symbol_prefix("a") + .symbol_suffix("bc") + .decimal_delimiter("x") + .group_delimiter("y") + .groups([2, 2, 2, 3]) + .nully("N/A") + .si_prefix(f.Prefix.none) + ) self.validate_complex(res.to_plotly_json()) def test_money_template(self): res = FormatTemplate.money(2).to_plotly_json() - self.assertEqual(res['specifier'], '$,.2f') + self.assertEqual(res["specifier"], "$,.2f") def test_percentage_template(self): res = FormatTemplate.percentage(1).to_plotly_json() - self.assertEqual(res['specifier'], '.1%') + self.assertEqual(res["specifier"], ".1%") def test_valid_align_named(self): Format().align(f.Align.center) def test_valid_align_string(self): - Format().align('=') + Format().align("=") def test_invalid_align_string(self): - self.assertRaises(TypeError, Format().align, 'i') + self.assertRaises(TypeError, Format().align, "i") def test_invalid_align_type(self): self.assertRaises(TypeError, Format().align, 7) def test_valid_fill(self): - Format().fill('.') + Format().fill(".") def test_invalid_fill_length(self): - self.assertRaises(ValueError, Format().fill, 'invalid') + self.assertRaises(ValueError, Format().fill, "invalid") def test_invalid_fill_type(self): self.assertRaises(TypeError, Format().fill, 7) @@ -76,7 +95,7 @@ def test_valid_group_bool(self): Format().group(True) def test_valid_group_string(self): - Format().group(',') + Format().group(",") def test_valid_group_named(self): Format().group(f.Group.no) @@ -85,13 +104,13 @@ def test_invalid_group_type(self): self.assertRaises(TypeError, Format().group, 7) def test_invalid_group_string(self): - self.assertRaises(TypeError, Format().group, 'invalid') + self.assertRaises(TypeError, Format().group, "invalid") def test_valid_padding_bool(self): Format().padding(False) def test_valid_padding_string(self): - Format().padding('0') + Format().padding("0") def test_valid_padding_named(self): Format().padding(f.Padding.no) @@ -100,7 +119,7 @@ def test_invalid_padding_type(self): self.assertRaises(TypeError, Format().padding, 7) def test_invalid_padding_string(self): - self.assertRaises(TypeError, Format().padding, 'invalid') + self.assertRaises(TypeError, Format().padding, "invalid") def test_valid_padding_width(self): Format().padding_width(10) @@ -127,61 +146,61 @@ def test_invalid_precision_type(self): self.assertRaises(TypeError, Format().precision, 7.7) def test_valid_prefix_number(self): - Format().si_prefix(10**-24) + Format().si_prefix(10 ** -24) def test_valid_prefix_named(self): Format().si_prefix(f.Prefix.micro) def test_invalid_prefix_number(self): - self.assertRaises(TypeError, Format().si_prefix, 10**-23) + self.assertRaises(TypeError, Format().si_prefix, 10 ** -23) def test_invalid_prefix_type(self): - self.assertRaises(TypeError, Format().si_prefix, '10**-23') + self.assertRaises(TypeError, Format().si_prefix, "10**-23") def test_valid_scheme_string(self): - Format().scheme('s') + Format().scheme("s") def test_valid_scheme_named(self): Format().scheme(f.Scheme.decimal) def test_invalid_scheme_string(self): - self.assertRaises(TypeError, Format().scheme, 'invalid') + self.assertRaises(TypeError, Format().scheme, "invalid") def test_invalid_scheme_type(self): self.assertRaises(TypeError, Format().scheme, 7) def test_valid_sign_string(self): - Format().sign('+') + Format().sign("+") def test_valid_sign_named(self): Format().sign(f.Sign.space) def test_invalid_sign_string(self): - self.assertRaises(TypeError, Format().sign, 'invalid') + self.assertRaises(TypeError, Format().sign, "invalid") def test_invalid_sign_type(self): self.assertRaises(TypeError, Format().sign, 7) def test_valid_symbol_string(self): - Format().symbol('$') + Format().symbol("$") def test_valid_symbol_named(self): Format().symbol(f.Symbol.hex) def test_invalid_symbol_string(self): - self.assertRaises(TypeError, Format().symbol, 'invalid') + self.assertRaises(TypeError, Format().symbol, "invalid") def test_invalid_symbol_type(self): self.assertRaises(TypeError, Format().symbol, 7) def test_valid_symbol_prefix(self): - Format().symbol_prefix('abc+-') + Format().symbol_prefix("abc+-") def test_invalid_symbol_prefix_type(self): self.assertRaises(TypeError, Format().symbol_prefix, 7) def test_valid_symbol_suffix(self): - Format().symbol_suffix('abc+-') + Format().symbol_suffix("abc+-") def test_invalid_symbol_suffix(self): self.assertRaises(TypeError, Format().symbol_suffix, 7) @@ -190,31 +209,31 @@ def test_valid_trim_boolean(self): Format().trim(False) def test_valid_trim_string(self): - Format().trim('~') + Format().trim("~") def test_valid_trim_named(self): Format().trim(f.Trim.yes) def test_invalid_trim_string(self): - self.assertRaises(TypeError, Format().trim, 'invalid') + self.assertRaises(TypeError, Format().trim, "invalid") def test_invalid_trim_type(self): self.assertRaises(TypeError, Format().trim, 7) def test_valid_decimal_delimiter(self): - Format().decimal_delimiter('x') + Format().decimal_delimiter("x") def test_valid_decimal_delimiter(self): - self.assertRaises(ValueError, Format().decimal_delimiter, 'xyz') + self.assertRaises(ValueError, Format().decimal_delimiter, "xyz") def test_invalid_decimal_delimiter(self): self.assertRaises(TypeError, Format().decimal_delimiter, 7) def test_valid_group_delimiator(self): - Format().group_delimiter('y') + Format().group_delimiter("y") def test_valid_group_delimiator(self): - self.assertRaises(ValueError, Format().group_delimiter, 'xyz') + self.assertRaises(ValueError, Format().group_delimiter, "xyz") def test_invalid_group_delimiter(self): self.assertRaises(TypeError, Format().group_delimiter, 7) @@ -247,4 +266,4 @@ def test_invalid_groups_nested_0(self): self.assertRaises(ValueError, Format().groups, [3, 3, 0]) def test_invalid_groups_nested_negative(self): - self.assertRaises(ValueError, Format().groups, [3, 3, -7]) \ No newline at end of file + self.assertRaises(ValueError, Format().groups, [3, 3, -7]) From ea38a8604e6ab7540b8e13eb2e8c5e5401a0f059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:23:02 -0500 Subject: [PATCH 29/38] mark skip instead of commenting out tests --- tests/selenium/test_basic_operations.py | 80 ++++++++++++------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 7b29fbf3e..134559e93 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -212,15 +212,15 @@ def test_tbst016_delete_cell(test): assert target.cell.get_text(0, 1) == "" -# # https://github.com/plotly/dash-table/issues/700 -# def test_tbst017_delete_cell_updates_while_selected(test): -# test.start_server(get_app()) +@pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") +def test_tbst017_delete_cell_updates_while_selected(test): + test.start_server(get_app()) -# with test.table('table') as target: -# target.cell.click(0, 1) -# test.send_keys(Keys.BACKSPACE) + with test.table('table') as target: + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) -# assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == '' def test_tbst018_delete_multiple_cells(test): @@ -242,20 +242,20 @@ def test_tbst018_delete_multiple_cells(test): assert target.cell.get_text(row, col) == "" -# # https://github.com/plotly/dash-table/issues/700 -# def test_tbst019_delete_multiple_cells_while_selected(test): -# test.start_server(get_app()) +@pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") +def test_tbst019_delete_multiple_cells_while_selected(test): + test.start_server(get_app()) -# with test.table('table') as target: -# target.cell.click(0, 1) -# with test.hold(Keys.SHIFT): -# ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + with test.table('table') as target: + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() -# ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() -# for row in range(2): -# for col in range(1, 3): -# assert target.cell.get_text(row, col) == '' + for row in range(2): + for col in range(1, 3): + assert target.cell.get_text(row, col) == '' def test_tbst020_sorted_table_delete_cell(test): @@ -272,18 +272,18 @@ def test_tbst020_sorted_table_delete_cell(test): assert target.cell.get_text(0, 1) == "" -# # https://github.com/plotly/dash-table/issues/700 -# def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): -# test.start_server(get_app()) +@pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") +def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): + test.start_server(get_app()) -# with test.table('table') as target: -# target.column.sort(0, rawDf.columns[0]) # None -> ASC -# target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC -# target.cell.click(0, 1) -# test.send_keys(Keys.BACKSPACE) + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) -# assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == '' def test_tbst022_sorted_table_delete_multiple_cells(test): @@ -308,20 +308,20 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): assert target.cell.get_text(row, col) == "" -# # https://github.com/plotly/dash-table/issues/700 -# def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): -# test.start_server(get_app()) +@pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") +def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): + test.start_server(get_app()) -# with test.table('table') as target: -# target.column.sort(0, rawDf.columns[0]) # None -> ASC -# target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table('table') as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC -# target.cell.click(0, 1) -# with test.hold(Keys.SHIFT): -# ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() -# ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() -# for row in range(2): -# for col in range(1, 3): -# assert target.cell.get_text(row, col) == '' + for row in range(2): + for col in range(1, 3): + assert target.cell.get_text(row, col) == '' From f84e8a0380d0646a181304c05764189b7a348d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:32:02 -0500 Subject: [PATCH 30/38] dash dev branch --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 28a02c4a0..b5624d095 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,7 @@ jobs: python -m venv venv || virtualenv venv . venv/bin/activate pip install -r dev-requirements.txt --quiet - git clone --depth 1 -b dt-582-new-selenium-tests git@github.com:plotly/dash.git dash-main + git clone --depth 1 git@github.com:plotly/dash.git dash-main pip install -e ./dash-main[dev,testing] --quiet cd dash-main/dash-renderer && npm run build && pip install -e . && cd ./../.. From ebb6ca591a8f530422c685e1f627e06c497d5de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:35:10 -0500 Subject: [PATCH 31/38] lint --- tests/selenium/test_basic_operations.py | 32 ++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 134559e93..3a6aabfa5 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -216,11 +216,11 @@ def test_tbst016_delete_cell(test): def test_tbst017_delete_cell_updates_while_selected(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 1) test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == "" def test_tbst018_delete_multiple_cells(test): @@ -246,16 +246,18 @@ def test_tbst018_delete_multiple_cells(test): def test_tbst019_delete_multiple_cells_while_selected(test): test.start_server(get_app()) - with test.table('table') as target: + with test.table("table") as target: target.cell.click(0, 1) with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( + Keys.RIGHT + ).perform() ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == '' + assert target.cell.get_text(row, col) == "" def test_tbst020_sorted_table_delete_cell(test): @@ -276,14 +278,14 @@ def test_tbst020_sorted_table_delete_cell(test): def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC target.cell.click(0, 1) test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == '' + assert target.cell.get_text(0, 1) == "" def test_tbst022_sorted_table_delete_multiple_cells(test): @@ -312,16 +314,18 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): test.start_server(get_app()) - with test.table('table') as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + with test.table("table") as target: + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC target.cell.click(0, 1) with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( + Keys.RIGHT + ).perform() ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == '' + assert target.cell.get_text(row, col) == "" From 0455339425ae1d18a8c2f11fd2035dc73813cfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:45:43 -0500 Subject: [PATCH 32/38] import pytest --- tests/selenium/test_basic_operations.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 3a6aabfa5..1a8577a4b 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -6,6 +6,7 @@ from selenium.webdriver.common.action_chains import ActionChains import pandas as pd +import pytest url = "https://github.com/plotly/datasets/raw/master/" "26k-consumer-complaints.csv" rawDf = pd.read_csv(url) From 225755f83eb99f2e0c13ddc2590f2e63157feaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:46:03 -0500 Subject: [PATCH 33/38] black --- dash_table/Format.py | 273 ++++++++++++++++------------------- dash_table/FormatTemplate.py | 9 +- 2 files changed, 131 insertions(+), 151 deletions(-) diff --git a/dash_table/Format.py b/dash_table/Format.py index 4ae06ace3..8dd12638c 100644 --- a/dash_table/Format.py +++ b/dash_table/Format.py @@ -5,111 +5,97 @@ def get_named_tuple(name, dict): return collections.namedtuple(name, dict.keys())(*dict.values()) -Align = get_named_tuple('align', { - 'default': '', - 'left': '<', - 'right': '>', - 'center': '^', - "right_sign": '=' -}) - -Group = get_named_tuple('group', { - 'no': '', - 'yes': ',' -}) - -Padding = get_named_tuple('padding', { - 'no': '', - 'yes': '0' -}) - -Prefix = get_named_tuple('prefix', { - 'yocto': 10 ** -24, - 'zepto': 10 ** -21, - 'atto': 10 ** -18, - 'femto': 10 ** -15, - 'pico': 10 ** -12, - 'nano': 10 ** -9, - 'micro': 10 ** -6, - 'milli': 10 ** -3, - 'none': None, - 'kilo': 10 ** 3, - 'mega': 10 ** 6, - 'giga': 10 ** 9, - 'tera': 10 ** 12, - 'peta': 10 ** 15, - 'exa': 10 ** 18, - 'zetta': 10 ** 21, - 'yotta': 10 ** 24 -}) - -Scheme = get_named_tuple('scheme', { - 'default': '', - 'decimal': 'r', - 'decimal_integer': 'd', - 'decimal_or_exponent': 'g', - 'decimal_si_prefix': 's', - 'exponent': 'e', - 'fixed': 'f', - 'percentage': '%', - 'percentage_rounded': 'p', - 'binary': 'b', - 'octal': 'o', - 'lower_case_hex': 'x', - 'upper_case_hex': 'X', - 'unicode': 'c' -}) - -Sign = get_named_tuple('sign', { - 'default': '', - 'negative': '-', - 'positive': '+', - 'parantheses': '(', - 'space': ' ' -}) - -Symbol = get_named_tuple('symbol', { - 'no': '', - 'yes': '$', - 'binary': '#b', - 'octal': '#o', - 'hex': '#x' -}) - -Trim = get_named_tuple('trim', { - 'no': '', - 'yes': '~' -}) - - -class Format(): +Align = get_named_tuple( + "align", + {"default": "", "left": "<", "right": ">", "center": "^", "right_sign": "="}, +) + +Group = get_named_tuple("group", {"no": "", "yes": ","}) + +Padding = get_named_tuple("padding", {"no": "", "yes": "0"}) + +Prefix = get_named_tuple( + "prefix", + { + "yocto": 10 ** -24, + "zepto": 10 ** -21, + "atto": 10 ** -18, + "femto": 10 ** -15, + "pico": 10 ** -12, + "nano": 10 ** -9, + "micro": 10 ** -6, + "milli": 10 ** -3, + "none": None, + "kilo": 10 ** 3, + "mega": 10 ** 6, + "giga": 10 ** 9, + "tera": 10 ** 12, + "peta": 10 ** 15, + "exa": 10 ** 18, + "zetta": 10 ** 21, + "yotta": 10 ** 24, + }, +) + +Scheme = get_named_tuple( + "scheme", + { + "default": "", + "decimal": "r", + "decimal_integer": "d", + "decimal_or_exponent": "g", + "decimal_si_prefix": "s", + "exponent": "e", + "fixed": "f", + "percentage": "%", + "percentage_rounded": "p", + "binary": "b", + "octal": "o", + "lower_case_hex": "x", + "upper_case_hex": "X", + "unicode": "c", + }, +) + +Sign = get_named_tuple( + "sign", + {"default": "", "negative": "-", "positive": "+", "parantheses": "(", "space": " "}, +) + +Symbol = get_named_tuple( + "symbol", {"no": "", "yes": "$", "binary": "#b", "octal": "#o", "hex": "#x"} +) + +Trim = get_named_tuple("trim", {"no": "", "yes": "~"}) + + +class Format: def __init__(self, **kwargs): self._locale = {} - self._nully = '' + self._nully = "" self._prefix = Prefix.none self._specifier = { - 'align': Align.default, - 'fill': '', - 'group': Group.no, - 'width': '', - 'padding': Padding.no, - 'precision': '', - 'sign': Sign.default, - 'symbol': Symbol.no, - 'trim': Trim.no, - 'type': Scheme.default + "align": Align.default, + "fill": "", + "group": Group.no, + "width": "", + "padding": Padding.no, + "precision": "", + "sign": Sign.default, + "symbol": Symbol.no, + "trim": Trim.no, + "type": Scheme.default, } valid_methods = [ - m for m in dir(self.__class__) - if m[0] != '_' and m != 'to_plotly_json' + m for m in dir(self.__class__) if m[0] != "_" and m != "to_plotly_json" ] for kw, val in kwargs.items(): if kw not in valid_methods: raise TypeError( - '{0} is not a format method. Expected one of'.format(kw), - str(list(valid_methods)) + "{0} is not a format method. Expected one of".format(kw), + str(list(valid_methods)), ) getattr(self, kw)(val) @@ -118,38 +104,37 @@ def _validate_char(self, value): self._validate_string(value) if len(value) != 1: - raise ValueError('expected value to a string of length one') + raise ValueError("expected value to a string of length one") def _validate_non_negative_integer_or_none(self, value): if value is None: return if not isinstance(value, int): - raise TypeError('expected value to be an integer') + raise TypeError("expected value to be an integer") if value < 0: - raise ValueError('expected value to be non-negative', str(value)) + raise ValueError("expected value to be non-negative", str(value)) def _validate_named(self, value, named_values): if value not in named_values: - raise TypeError('expected value to be one of', - str(list(named_values))) + raise TypeError("expected value to be one of", str(list(named_values))) def _validate_string(self, value): - if not isinstance(value, (str, u''.__class__)): - raise TypeError('expected value to be a string') + if not isinstance(value, (str, u"".__class__)): + raise TypeError("expected value to be a string") # Specifier def align(self, value): self._validate_named(value, Align) - self._specifier['align'] = value + self._specifier["align"] = value return self def fill(self, value): self._validate_char(value) - self._specifier['fill'] = value + self._specifier["fill"] = value return self def group(self, value): @@ -158,7 +143,7 @@ def group(self, value): self._validate_named(value, Group) - self._specifier['group'] = value + self._specifier["group"] = value return self def padding(self, value): @@ -167,39 +152,37 @@ def padding(self, value): self._validate_named(value, Padding) - self._specifier['padding'] = value + self._specifier["padding"] = value return self def padding_width(self, value): self._validate_non_negative_integer_or_none(value) - self._specifier['width'] = value if value is not None else '' + self._specifier["width"] = value if value is not None else "" return self def precision(self, value): self._validate_non_negative_integer_or_none(value) - self._specifier['precision'] = ( - '.{0}'.format(value) if value is not None else '' - ) + self._specifier["precision"] = ".{0}".format(value) if value is not None else "" return self def scheme(self, value): self._validate_named(value, Scheme) - self._specifier['type'] = value + self._specifier["type"] = value return self def sign(self, value): self._validate_named(value, Sign) - self._specifier['sign'] = value + self._specifier["sign"] = value return self def symbol(self, value): self._validate_named(value, Symbol) - self._specifier['symbol'] = value + self._specifier["symbol"] = value return self def trim(self, value): @@ -208,66 +191,66 @@ def trim(self, value): self._validate_named(value, Trim) - self._specifier['trim'] = value + self._specifier["trim"] = value return self # Locale def symbol_prefix(self, value): self._validate_string(value) - if 'symbol' not in self._locale: - self._locale['symbol'] = [value, ''] + if "symbol" not in self._locale: + self._locale["symbol"] = [value, ""] else: - self._locale['symbol'][0] = value + self._locale["symbol"][0] = value return self def symbol_suffix(self, value): self._validate_string(value) - if 'symbol' not in self._locale: - self._locale['symbol'] = ['', value] + if "symbol" not in self._locale: + self._locale["symbol"] = ["", value] else: - self._locale['symbol'][1] = value + self._locale["symbol"][1] = value return self def decimal_delimiter(self, value): self._validate_char(value) - self._locale['decimal'] = value + self._locale["decimal"] = value return self def group_delimiter(self, value): self._validate_char(value) - self._locale['group'] = value + self._locale["group"] = value return self def groups(self, groups): groups = ( - groups if isinstance(groups, list) else - [groups] if isinstance(groups, int) else None + groups + if isinstance(groups, list) + else [groups] + if isinstance(groups, int) + else None ) if not isinstance(groups, list): - raise TypeError( - 'expected groups to be an integer or a list of integers' - ) + raise TypeError("expected groups to be an integer or a list of integers") if len(groups) == 0: raise ValueError( - 'expected groups to be an integer or a list of ' - 'one or more integers' + "expected groups to be an integer or a list of " "one or more integers" ) for group in groups: if not isinstance(group, int): - raise TypeError('expected entry to be an integer') + raise TypeError("expected entry to be an integer") if group <= 0: - raise ValueError('expected entry to be a non-negative integer') + raise ValueError("expected entry to be a non-negative integer") - self._locale['grouping'] = groups + self._locale["grouping"] = groups return self # Nully @@ -284,21 +267,21 @@ def si_prefix(self, value): def to_plotly_json(self): f = {} - f['locale'] = self._locale.copy() - f['nully'] = self._nully - f['prefix'] = self._prefix - aligned = self._specifier['align'] != Align.default - f['specifier'] = '{}{}{}{}{}{}{}{}{}{}'.format( - self._specifier['fill'] if aligned else '', - self._specifier['align'], - self._specifier['sign'], - self._specifier['symbol'], - self._specifier['padding'], - self._specifier['width'], - self._specifier['group'], - self._specifier['precision'], - self._specifier['trim'], - self._specifier['type'] + f["locale"] = self._locale.copy() + f["nully"] = self._nully + f["prefix"] = self._prefix + aligned = self._specifier["align"] != Align.default + f["specifier"] = "{}{}{}{}{}{}{}{}{}{}".format( + self._specifier["fill"] if aligned else "", + self._specifier["align"], + self._specifier["sign"], + self._specifier["symbol"], + self._specifier["padding"], + self._specifier["width"], + self._specifier["group"], + self._specifier["precision"], + self._specifier["trim"], + self._specifier["type"], ) return f diff --git a/dash_table/FormatTemplate.py b/dash_table/FormatTemplate.py index 9991437c7..9c2688ca8 100644 --- a/dash_table/FormatTemplate.py +++ b/dash_table/FormatTemplate.py @@ -7,16 +7,13 @@ def money(decimals, sign=Sign.default): precision=decimals, scheme=Scheme.fixed, sign=sign, - symbol=Symbol.yes + symbol=Symbol.yes, ) def percentage(decimals, rounded=False): if not isinstance(rounded, bool): - raise TypeError('expected rounded to be a boolean') + raise TypeError("expected rounded to be a boolean") rounded = Scheme.percentage_rounded if rounded else Scheme.percentage - return Format( - scheme=rounded, - precision=decimals - ) + return Format(scheme=rounded, precision=decimals) From 854276e6f807c967ae411af8e5c86d5fd1e7b2f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 14:56:26 -0500 Subject: [PATCH 34/38] copy/paste with hold/send_keys --- tests/selenium/conftest.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 3777e20df..fab73afb2 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -289,14 +289,12 @@ def table(self, id): return DataTableContext(DataTableFacade(id, self)) def copy(self): - ActionChains(self.driver).key_down(Keys.CONTROL).send_keys("c").key_up( - Keys.CONTROL - ).perform() + with self.hold(Keys.CONTROL): + self.send_keys("c") def paste(self): - ActionChains(self.driver).key_down(Keys.CONTROL).send_keys("v").key_up( - Keys.CONTROL - ).perform() + with self.hold(Keys.CONTROL): + self.send_keys("v") @preconditions(_validate_key) def hold(self, key): From 6c36f1d77f935d78004f595dada5b425d3d9652b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 15:13:19 -0500 Subject: [PATCH 35/38] remove DataTableContext, refactor --- tests/selenium/conftest.py | 14 +- tests/selenium/test_basic_copy_paste.py | 187 +++-- tests/selenium/test_basic_operations.py | 267 +++--- tests/selenium/test_derived_props.py | 895 +++++++++++---------- tests/selenium/test_editable.py | 122 +-- tests/selenium/test_markdown_copy_paste.py | 68 +- tests/selenium/test_pagination.py | 130 +-- 7 files changed, 849 insertions(+), 834 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index fab73afb2..6f1d7a409 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -26,18 +26,6 @@ _TIMEOUT = 10 -class DataTableContext: - @preconditions(_validate_target) - def __init__(self, target): - self.target = target - - def __enter__(self): - return self.target - - def __exit__(self, type, value, traceback): - return - - class HoldKeyContext: @preconditions(_validate_mixin, _validate_key) def __init__(self, mixin, key): @@ -286,7 +274,7 @@ def _wait_for_table(self, id, state=_ANY): @preconditions(_validate_id) def table(self, id): - return DataTableContext(DataTableFacade(id, self)) + return DataTableFacade(id, self) def copy(self): with self.hold(Keys.CONTROL): diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index 8c6cc912d..e8de4789e 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -75,153 +75,152 @@ def update_data(timestamp, current, previous): def test_tbcp001_copy_paste_callback(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 0) + target = test.table("table") + target.cell.click(0, 0) - test.copy() - target.cell.click(1, 0) - test.paste() + test.copy() + target.cell.click(1, 0) + test.paste() - assert target.cell.get_text(1, 0) == "0" - assert target.cell.get_text(1, 1) == "MODIFIED" + assert target.cell.get_text(1, 0) == "0" + assert target.cell.get_text(1, 1) == "MODIFIED" def test_tbcp002_sorted_copy_paste_callback(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[2]) + target = test.table("table") + target.column.sort(0, rawDf.columns[2]) - assert target.cell.get_text(0, 0) == "11" + assert target.cell.get_text(0, 0) == "11" - target.cell.click(0, 0) + target.cell.click(0, 0) - test.copy() - target.cell.click(1, 0) - test.paste() + test.copy() + target.cell.click(1, 0) + test.paste() - assert target.cell.get_text(1, 0) == "11" - assert target.cell.get_text(1, 1) == "MODIFIED" + assert target.cell.get_text(1, 0) == "11" + assert target.cell.get_text(1, 1) == "MODIFIED" - target.cell.click(1, 1) + target.cell.click(1, 1) - test.copy() - target.cell.click(2, 1) - test.paste() + test.copy() + target.cell.click(2, 1) + test.paste() - assert target.cell.get_text(1, 0) == "11" - assert target.cell.get_text(2, 1) == "MODIFIED" + assert target.cell.get_text(1, 0) == "11" + assert target.cell.get_text(2, 1) == "MODIFIED" def test_tbcp003_copy_multiple_rows(test): test.start_server(get_app()) - with test.table("table") as target: - with test.hold(Keys.SHIFT): - target.cell.click(0, 0) - target.cell.click(2, 0) + target = test.table("table") + with test.hold(Keys.SHIFT): + target.cell.click(0, 0) + target.cell.click(2, 0) - test.copy() - target.cell.click(3, 0) - test.paste() + test.copy() + target.cell.click(3, 0) + test.paste() - for i in range(3): - assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) - assert target.cell.get_text(i + 3, 1) == "MODIFIED" + for i in range(3): + assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) + assert target.cell.get_text(i + 3, 1) == "MODIFIED" def test_tbcp004_copy_9_and_10(test): test.start_server(get_app()) - with test.table("table") as source, test.table("table2") as target: - source.cell.click(9, 0) - with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).perform() + source = test.table("table") + target = test.table("table2") - test.copy() - target.cell.click(0, 0) - test.paste() + source.cell.click(9, 0) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).perform() + + test.copy() + target.cell.click(0, 0) + test.paste() - for row in range(2): - for col in range(1): - assert target.cell.get_text(row, col) == source.cell.get_text( - row + 9, col - ) + for row in range(2): + for col in range(1): + assert target.cell.get_text(row, col) == source.cell.get_text(row + 9, col) def test_tbcp005_copy_multiple_rows_and_columns(test): test.start_server(get_app()) - with test.table("table") as table: - table.cell.click(0, 1) - with test.hold(Keys.SHIFT): - table.cell.click(2, 2) + target = test.table("table") + + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + target.cell.click(2, 2) - test.copy() - table.cell.click(3, 1) - test.paste() + test.copy() + target.cell.click(3, 1) + test.paste() - for row in range(3): - for col in range(1, 3): - assert table.cell.get_text(row + 3, col) == table.cell.get_text( - row, col - ) + for row in range(3): + for col in range(1, 3): + assert target.cell.get_text(row + 3, col) == target.cell.get_text(row, col) def test_tbcp006_copy_paste_between_tables(test): test.start_server(get_app()) - with test.table("table") as source, test.table("table2") as target: - source.cell.click(10, 0) - with test.hold(Keys.SHIFT): - source.cell.click(13, 3) + source = test.table("table") + target = test.table("table2") - test.copy() - target.cell.click(0, 0) - test.paste() + source.cell.click(10, 0) + with test.hold(Keys.SHIFT): + source.cell.click(13, 3) + + test.copy() + target.cell.click(0, 0) + test.paste() - for row in range(4): - for col in range(4): - assert source.cell.get_text(row + 10, col) == target.cell.get_text( - row, col - ) + for row in range(4): + for col in range(4): + assert source.cell.get_text(row + 10, col) == target.cell.get_text(row, col) def test_tbcp007_copy_paste_with_hidden_column(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.hide(0, "Complaint ID") - target.cell.click(0, 0) - with test.hold(Keys.SHIFT): - target.cell.click(2, 2) + target = test.table("table") - test.copy() - target.cell.click(3, 1) - test.paste() + target.column.hide(0, "Complaint ID") + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + target.cell.click(2, 2) - for row in range(3): - for col in range(3): - assert target.cell.get_text(row, col) == target.cell.get_text( - row + 3, col + 1 - ) + test.copy() + target.cell.click(3, 1) + test.paste() + + for row in range(3): + for col in range(3): + assert target.cell.get_text(row, col) == target.cell.get_text( + row + 3, col + 1 + ) def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.hide(0, "Complaint ID") - target.cell.click(10, 0) - with test.hold(Keys.SHIFT): - target.cell.click(13, 2) + target = test.table("table") - test.copy() - target.cell.click(0, 0) - test.paste() + target.column.hide(0, "Complaint ID") + target.cell.click(10, 0) + with test.hold(Keys.SHIFT): + target.cell.click(13, 2) + + test.copy() + target.cell.click(0, 0) + test.paste() - for row in range(4): - for col in range(3): - assert target.cell.get_text(row + 10, col) == target.cell.get_text( - row, col - ) + for row in range(4): + for col in range(3): + assert target.cell.get_text(row + 10, col) == target.cell.get_text(row, col) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 1a8577a4b..d881f9b35 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -36,207 +36,221 @@ def get_app(): def test_tbst001_get_cell(test): test.start_server(get_app()) - with test.table("table") as target: - assert target.cell.get_text(0, 0) == "0" - target.paging.click_next_page() - assert target.cell.get_text(0, 0) == "250" + target = test.table("table") + + assert target.cell.get_text(0, 0) == "0" + target.paging.click_next_page() + assert target.cell.get_text(0, 0) == "250" def test_tbst002_select_all_text(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 1) + target = test.table("table") + + target.cell.click(0, 1) - assert target.cell.get_text(0, 1) == test.get_selected_text() + assert target.cell.get_text(0, 1) == test.get_selected_text() # https://github.com/plotly/dash-table/issues/50 def test_tbst003_edit_on_enter(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc" + Keys.ENTER) + target = test.table("table") + + target.cell.click(249, 0) + test.send_keys("abc" + Keys.ENTER) - assert target.cell.get_text(249, 0) == "abc" + assert target.cell.get_text(249, 0) == "abc" # https://github.com/plotly/dash-table/issues/107 def test_tbst004_edit_on_tab(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc" + Keys.TAB) + target = test.table("table") - assert target.cell.get_text(249, 0) == "abc" + target.cell.click(249, 0) + test.send_keys("abc" + Keys.TAB) + + assert target.cell.get_text(249, 0) == "abc" def test_tbst005_edit_last_row_on_click_outside(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc") - target.cell.click(248, 0) + target = test.table("table") + + target.cell.click(249, 0) + test.send_keys("abc") + target.cell.click(248, 0) - assert target.cell.get_text(249, 0) == "abc" + assert target.cell.get_text(249, 0) == "abc" # https://github.com/plotly/dash-table/issues/141 def test_tbst006_focused_arrow_left(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 1) - test.send_keys("abc" + Keys.LEFT) + target = test.table("table") + + target.cell.click(249, 1) + test.send_keys("abc" + Keys.LEFT) - assert target.cell.get_text(249, 1) == "abc" - assert target.cell.is_focused(249, 0) + assert target.cell.get_text(249, 1) == "abc" + assert target.cell.is_focused(249, 0) # https://github.com/plotly/dash-table/issues/141 def test_tbst007_active_focused_arrow_right(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc" + Keys.RIGHT) + target = test.table("table") - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(249, 1) + target.cell.click(249, 0) + test.send_keys("abc" + Keys.RIGHT) + + assert target.cell.get_text(249, 0) == "abc" + assert target.cell.is_focused(249, 1) # https://github.com/plotly/dash-table/issues/141 def test_tbst008_active_focused_arrow_up(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc" + Keys.UP) + target = test.table("table") + + target.cell.click(249, 0) + test.send_keys("abc" + Keys.UP) - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(248, 0) + assert target.cell.get_text(249, 0) == "abc" + assert target.cell.is_focused(248, 0) # https://github.com/plotly/dash-table/issues/141 def test_tbst009_active_focused_arrow_down(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(249, 0) - test.send_keys("abc" + Keys.DOWN) + target = test.table("table") + + target.cell.click(249, 0) + test.send_keys("abc" + Keys.DOWN) - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(249, 0) + assert target.cell.get_text(249, 0) == "abc" + assert target.cell.is_focused(249, 0) def test_tbst010_active_with_dblclick(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.double_click(0, 0) - assert target.cell.is_active(0, 0) - assert target.cell.get_text(0, 0) == test.get_selected_text() + target = test.table("table") + + target.cell.double_click(0, 0) + assert target.cell.is_active(0, 0) + assert target.cell.get_text(0, 0) == test.get_selected_text() def test_tbst011_delete_row(test): test.start_server(get_app()) - with test.table("table") as target: - text01 = target.cell.get_text(1, 0) - target.row.delete(0) + target = test.table("table") + + text01 = target.cell.get_text(1, 0) + target.row.delete(0) - assert target.cell.get_text(0, 0) == text01 + assert target.cell.get_text(0, 0) == text01 def test_tbst012_delete_sorted_row(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target = test.table("table") + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - text01 = target.cell.get_text(1, 0) - target.row.delete(0) + text01 = target.cell.get_text(1, 0) + target.row.delete(0) - assert target.cell.get_text(0, 0) == text01 + assert target.cell.get_text(0, 0) == text01 def test_tbst013_select_row(test): test.start_server(get_app()) - with test.table("table") as target: - target.row.select(0) + target = test.table("table") - assert target.row.is_selected(0) + target.row.select(0) + + assert target.row.is_selected(0) def test_tbst014_selected_sorted_row(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - target.row.select(0) + target = test.table("table") + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.row.select(0) - assert target.row.is_selected(0) + assert target.row.is_selected(0) def test_tbst015_selected_row_respects_sort(test): test.start_server(get_app()) - with test.table("table") as target: - target.row.select(0) + target = test.table("table") + + target.row.select(0) - assert target.row.is_selected(0) + assert target.row.is_selected(0) - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - assert not target.row.is_selected(0) + assert not target.row.is_selected(0) - target.column.sort(0, rawDf.columns[0]) # DESC -> None + target.column.sort(0, rawDf.columns[0]) # DESC -> None - assert target.row.is_selected(0) + assert target.row.is_selected(0) def test_tbst016_delete_cell(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 1) - test.send_keys(Keys.BACKSPACE) - test.send_keys(Keys.ENTER) + target = test.table("table") - assert target.cell.get_text(0, 1) == "" + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) + test.send_keys(Keys.ENTER) + + assert target.cell.get_text(0, 1) == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") def test_tbst017_delete_cell_updates_while_selected(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 1) - test.send_keys(Keys.BACKSPACE) + target = test.table("table") + + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == "" + assert target.cell.get_text(0, 1) == "" def test_tbst018_delete_multiple_cells(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 1) - with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( - Keys.RIGHT - ).perform() + target = test.table("table") + + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() - ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys( - Keys.ENTER - ).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() for row in range(2): for col in range(1, 3): @@ -247,14 +261,13 @@ def test_tbst018_delete_multiple_cells(test): def test_tbst019_delete_multiple_cells_while_selected(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 1) - with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( - Keys.RIGHT - ).perform() + target = test.table("table") - ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + + ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() for row in range(2): for col in range(1, 3): @@ -264,47 +277,46 @@ def test_tbst019_delete_multiple_cells_while_selected(test): def test_tbst020_sorted_table_delete_cell(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target = test.table("table") + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - target.cell.click(0, 1) - test.send_keys(Keys.BACKSPACE) - test.send_keys(Keys.ENTER) + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) + test.send_keys(Keys.ENTER) - assert target.cell.get_text(0, 1) == "" + assert target.cell.get_text(0, 1) == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target = test.table("table") + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - target.cell.click(0, 1) - test.send_keys(Keys.BACKSPACE) + target.cell.click(0, 1) + test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == "" + assert target.cell.get_text(0, 1) == "" def test_tbst022_sorted_table_delete_multiple_cells(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target = test.table("table") - target.cell.click(0, 1) - with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( - Keys.RIGHT - ).perform() + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys( - Keys.ENTER - ).perform() + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() + + ActionChains(test.driver).send_keys(Keys.BACKSPACE).send_keys(Keys.ENTER).perform() for row in range(2): for col in range(1, 3): @@ -315,17 +327,16 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): test.start_server(get_app()) - with test.table("table") as target: - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target = test.table("table") + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - target.cell.click(0, 1) - with test.hold(Keys.SHIFT): - ActionChains(test.driver).send_keys(Keys.DOWN).send_keys( - Keys.RIGHT - ).perform() + target.cell.click(0, 1) + with test.hold(Keys.SHIFT): + ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() - ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() + ActionChains(test.driver).send_keys(Keys.BACKSPACE).perform() for row in range(2): for col in range(1, 3): diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py index 9822dae76..4eb5d43bc 100644 --- a/tests/selenium/test_derived_props.py +++ b/tests/selenium/test_derived_props.py @@ -82,483 +82,486 @@ def show_props(*args): def test_tdrp001_select_rows(test): test.start_server(get_app()) - with test.table("table") as target: - target.row.select(0) - target.row.select(1) - - assert test.find_element("#active_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#start_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#end_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(2))) - assert test.find_element("#selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3002))) - - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(2))) - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3002))) - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(2))) - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3002))) - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) + target = test.table("table") + + target.row.select(0) + target.row.select(1) + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute("innerHTML") == json.dumps( + list(range(2)) + ) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(2))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(2))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3002))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp002_select_cell(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 0) + target = test.table("table") - active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) + target.cell.click(0, 0) - assert test.find_element("#active_cell").get_attribute( - "innerHTML" - ) == json.dumps(active) - assert test.find_element("#start_cell").get_attribute( - "innerHTML" - ) == json.dumps(active) - assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( - active - ) - assert test.find_element("#selected_cells").get_attribute( - "innerHTML" - ) == json.dumps([active]) - assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] + active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) + assert test.find_element("#active_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#start_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps([active]) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp003_select_cells(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 0) - with test.hold(Keys.SHIFT): - test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) - - active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) - - selected = [] - for row in range(3): - for col in range(3): - selected.append( - dict( - row=row, - column=col, - column_id=rawDf.columns[col], - row_id=row + 3000, - ) - ) + target = test.table("table") - assert test.find_element("#active_cell").get_attribute( - "innerHTML" - ) == json.dumps(active) - assert test.find_element("#start_cell").get_attribute( - "innerHTML" - ) == json.dumps(selected[0]) - assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( - selected[-1] - ) - assert test.find_element("#selected_cells").get_attribute( - "innerHTML" - ) == json.dumps(selected) - assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) - - # reduce selection - with test.hold(Keys.SHIFT): - test.send_keys(Keys.UP + Keys.LEFT) - - selected = [] - for row in range(2): - for col in range(2): - selected.append( - dict( - row=row, - column=col, - column_id=rawDf.columns[col], - row_id=row + 3000, - ) + active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) + + selected = [] + for row in range(3): + for col in range(3): + selected.append( + dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, ) + ) - assert test.find_element("#active_cell").get_attribute( - "innerHTML" - ) == json.dumps(active) - assert test.find_element("#start_cell").get_attribute( - "innerHTML" - ) == json.dumps(selected[0]) - assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( - selected[-1] - ) - assert test.find_element("#selected_cells").get_attribute( - "innerHTML" - ) == json.dumps(selected) - assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] + assert test.find_element("#active_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#start_cell").get_attribute("innerHTML") == json.dumps( + selected[0] + ) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + selected[-1] + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) + + # reduce selection + with test.hold(Keys.SHIFT): + test.send_keys(Keys.UP + Keys.LEFT) + + selected = [] + for row in range(2): + for col in range(2): + selected.append( + dict( + row=row, + column=col, + column_id=rawDf.columns[col], + row_id=row + 3000, + ) + ) - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) + assert test.find_element("#active_cell").get_attribute("innerHTML") == json.dumps( + active + ) + assert test.find_element("#start_cell").get_attribute("innerHTML") == json.dumps( + selected[0] + ) + assert test.find_element("#end_cell").get_attribute("innerHTML") == json.dumps( + selected[-1] + ) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) def test_tdrp004_navigate_selected_cells(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, 0) - with test.hold(Keys.SHIFT): - test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) - - selected = [] - for row in range(3): - for col in range(3): - selected.append( - dict( - row=row, - column=col, - column_id=rawDf.columns[col], - row_id=row + 3000, - ) - ) + target = test.table("table") + + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) - for row in range(3): - for col in range(3): - active = dict( + selected = [] + for row in range(3): + for col in range(3): + selected.append( + dict( row=row, column=col, column_id=rawDf.columns[col], row_id=row + 3000, ) - - assert test.find_element("#active_cell").get_attribute( - "innerHTML" - ) == json.dumps(active) - assert test.find_element("#start_cell").get_attribute( - "innerHTML" - ) == json.dumps(selected[0]) - assert test.find_element("#end_cell").get_attribute( - "innerHTML" - ) == json.dumps(selected[-1]) - assert test.find_element("#selected_cells").get_attribute( - "innerHTML" - ) == json.dumps(selected) - assert test.find_element("#selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - - assert test.find_element( - "#derived_viewport_selected_rows" - ).get_attribute("innerHTML") in ["None", json.dumps([])] - assert test.find_element( - "#derived_viewport_selected_row_ids" - ).get_attribute("innerHTML") in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element( - "#derived_virtual_selected_rows" - ).get_attribute("innerHTML") in ["None", json.dumps([])] - assert test.find_element( - "#derived_virtual_selected_row_ids" - ).get_attribute("innerHTML") in ["None", json.dumps([])] - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) - - test.send_keys(Keys.TAB) + ) + + for row in range(3): + for col in range(3): + active = dict( + row=row, column=col, column_id=rawDf.columns[col], row_id=row + 3000, + ) + + assert test.find_element("#active_cell").get_attribute( + "innerHTML" + ) == json.dumps(active) + assert test.find_element("#start_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[0]) + assert test.find_element("#end_cell").get_attribute( + "innerHTML" + ) == json.dumps(selected[-1]) + assert test.find_element("#selected_cells").get_attribute( + "innerHTML" + ) == json.dumps(selected) + assert test.find_element("#selected_rows").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element( + "#derived_viewport_selected_row_ids" + ).get_attribute("innerHTML") in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) + + test.send_keys(Keys.TAB) def test_tdrp005_filtered_and_sorted_row_select(test): test.start_server(get_app()) - with test.table("table") as target: - target.row.select(0) - target.row.select(1) - target.row.select(2) - - assert test.find_element("#active_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#start_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#end_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3))) - assert test.find_element("#selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003))) - - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3))) - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003))) - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(10))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3010))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3))) - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003))) - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(100))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100))) - - target.column.filter(rawDf.columns[0]) - test.send_keys("is even" + Keys.ENTER) - - assert test.find_element("#active_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#start_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#end_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3))) - assert test.find_element("#selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003))) - - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(0, 2))) - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(0, 20, 2))) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3020, 2))) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(0, 2))) - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(0, 100, 2))) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100, 2))) - - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - - assert test.find_element("#active_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#start_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#end_cell").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ - "None", - json.dumps([]), - ] - assert test.find_element("#selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3))) - assert test.find_element("#selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003))) - - assert test.find_element("#derived_viewport_selected_rows").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( - "innerHTML" - ) in ["None", json.dumps([])] - assert test.find_element("#derived_viewport_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(80, 100, 2))[::-1]) - assert test.find_element("#derived_viewport_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3080, 3100, 2))[::-1]) - - assert test.find_element("#derived_virtual_selected_rows").get_attribute( - "innerHTML" - ) == json.dumps(list(range(48, 50))[::-1]) - assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3003, 2))) - assert test.find_element("#derived_virtual_indices").get_attribute( - "innerHTML" - ) == json.dumps(list(range(0, 100, 2))[::-1]) - assert test.find_element("#derived_virtual_row_ids").get_attribute( - "innerHTML" - ) == json.dumps(list(range(3000, 3100, 2))[::-1]) + target = test.table("table") + + target.row.select(0) + target.row.select(1) + target.row.select(2) + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute("innerHTML") == json.dumps( + list(range(3)) + ) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(10))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3010))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(100))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100))) + + target.column.filter(rawDf.columns[0]) + test.send_keys("is even" + Keys.ENTER) + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute("innerHTML") == json.dumps( + list(range(3)) + ) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 2))) + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 20, 2))) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3020, 2))) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 2))) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 100, 2))) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100, 2))) + + target.column.sort(0, rawDf.columns[0]) # None -> ASC + target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + + assert test.find_element("#active_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#start_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#end_cell").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_cells").get_attribute("innerHTML") in [ + "None", + json.dumps([]), + ] + assert test.find_element("#selected_rows").get_attribute("innerHTML") == json.dumps( + list(range(3)) + ) + assert test.find_element("#selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003))) + + assert test.find_element("#derived_viewport_selected_rows").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_selected_row_ids").get_attribute( + "innerHTML" + ) in ["None", json.dumps([])] + assert test.find_element("#derived_viewport_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(80, 100, 2))[::-1]) + assert test.find_element("#derived_viewport_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3080, 3100, 2))[::-1]) + + assert test.find_element("#derived_virtual_selected_rows").get_attribute( + "innerHTML" + ) == json.dumps(list(range(48, 50))[::-1]) + assert test.find_element("#derived_virtual_selected_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3003, 2))) + assert test.find_element("#derived_virtual_indices").get_attribute( + "innerHTML" + ) == json.dumps(list(range(0, 100, 2))[::-1]) + assert test.find_element("#derived_virtual_row_ids").get_attribute( + "innerHTML" + ) == json.dumps(list(range(3000, 3100, 2))[::-1]) diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py index dd89ca967..29508c03e 100644 --- a/tests/selenium/test_editable.py +++ b/tests/selenium/test_editable.py @@ -69,17 +69,16 @@ def test_tedi001_loading_on_data_change(test): test.start_server(app) - with test.table("table") as target: - with blocking: - test.find_element("#blocking").click() - target.is_loading() - target.cell.click(0, 0) - assert ( - len(target.cell.get(0, 0).find_elements_by_css_selector("input")) == 0 - ) + target = test.table("table") - target.is_ready() - assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + with blocking: + test.find_element("#blocking").click() + target.is_loading() + target.cell.click(0, 0) + assert len(target.cell.get(0, 0).find_elements_by_css_selector("input")) == 0 + + target.is_ready() + assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None def test_tedi002_ready_on_non_data_change(test): @@ -87,45 +86,46 @@ def test_tedi002_ready_on_non_data_change(test): test.start_server(app) - with test.table("table") as target: - with blocking: - test.find_element("#non-blocking").click() - target.is_ready() - target.cell.click(0, 0) - assert ( - target.cell.get(0, 0).find_element_by_css_selector("input") is not None - ) + target = test.table("table") + with blocking: + test.find_element("#non-blocking").click() target.is_ready() + target.cell.click(0, 0) assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + target.is_ready() + assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + def test_tedi003_does_not_steal_focus(test): app, blocking, non_blocking = get_app_and_locks() test.start_server(app) - with test.table("table") as target: - with blocking: - test.find_element("#blocking").click() - test.find_element("#input").click() - assert test.find_element("#input") == test.driver.switch_to.active_element + target = test.table("table") - target.is_ready() + with blocking: + test.find_element("#blocking").click() + test.find_element("#input").click() assert test.find_element("#input") == test.driver.switch_to.active_element + target.is_ready() + assert test.find_element("#input") == test.driver.switch_to.active_element + def test_tedi004_edit_on_non_blocking(test): app, blocking, non_blocking = get_app_and_locks() test.start_server(app) - with test.table("table") as target: - with blocking: - test.find_element("#non-blocking").click() - target.cell.click(0, 0) - test.send_keys("abc" + Keys.ENTER) - assert target.cell.get_text(0, 0) == "abc" + target = test.table("table") + + with blocking: + test.find_element("#non-blocking").click() + target.cell.click(0, 0) + test.send_keys("abc" + Keys.ENTER) + assert target.cell.get_text(0, 0) == "abc" def test_tedi005_prevent_copy_paste_on_blocking(test): @@ -133,22 +133,23 @@ def test_tedi005_prevent_copy_paste_on_blocking(test): test.start_server(app) - with test.table("table") as target: - with blocking: - test.find_element("#blocking").click() - target.cell.click(0, 0) - with test.hold(Keys.SHIFT): - test.send_keys(Keys.DOWN + Keys.RIGHT) + target = test.table("table") + + with blocking: + test.find_element("#blocking").click() + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.RIGHT) - test.copy() - target.cell.click(2, 0) - test.paste() + test.copy() + target.cell.click(2, 0) + test.paste() - for row in range(2): - for col in range(2): - assert target.cell.get_text(row + 2, col) != target.cell.get_text( - row, col - ) + for row in range(2): + for col in range(2): + assert target.cell.get_text(row + 2, col) != target.cell.get_text( + row, col + ) def test_tedi006_allow_copy_paste_on_non_blocking(test): @@ -156,19 +157,20 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): test.start_server(app) - with test.table("table") as target: - with non_blocking: - test.find_element("#non-blocking").click() - target.cell.click(0, 0) - with test.hold(Keys.SHIFT): - test.send_keys(Keys.DOWN + Keys.RIGHT) - - test.copy() - target.cell.click(2, 0) - test.paste() - - for row in range(2): - for col in range(2): - assert target.cell.get_text(row + 2, col) == target.cell.get_text( - row, col - ) + target = test.table("table") + + with non_blocking: + test.find_element("#non-blocking").click() + target.cell.click(0, 0) + with test.hold(Keys.SHIFT): + test.send_keys(Keys.DOWN + Keys.RIGHT) + + test.copy() + target.cell.click(2, 0) + test.paste() + + for row in range(2): + for col in range(2): + assert target.cell.get_text(row + 2, col) == target.cell.get_text( + row, col + ) diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index 9536118e6..f882e9c46 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -41,59 +41,61 @@ def get_app(): def test_tmcp001_copy_markdown_to_text(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, "Issue") + target = test.table("table") - test.copy() - target.cell.click(0, "Sub-product") - test.paste() + target.cell.click(0, "Issue") - assert target.cell.get_text(0, 2) == df[0].get("Issue") + test.copy() + target.cell.click(0, "Sub-product") + test.paste() + + assert target.cell.get_text(0, 2) == df[0].get("Issue") def test_tmcp002_copy_markdown_to_markdown(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, "Product") + target = test.table("table") + + target.cell.click(0, "Product") - test.copy() - target.cell.click(0, "Complaint ID") - test.paste() + test.copy() + target.cell.click(0, "Complaint ID") + test.paste() - assert target.cell.get_text(0, "Complaint ID") == target.cell.get_text( - 0, "Product" - ) + assert target.cell.get_text(0, "Complaint ID") == target.cell.get_text(0, "Product") def test_tmcp003_copy_text_to_markdown(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(1, "Sub-product") + target = test.table("table") + + target.cell.click(1, "Sub-product") - test.copy() - target.cell.click(1, "Product") - test.paste() + test.copy() + target.cell.click(1, "Product") + test.paste() - assert target.cell.get(1, "Product").find_element_by_css_selector( - ".dash-cell-value > p" - ).get_attribute("innerHTML") == df[1].get("Sub-product") + assert target.cell.get(1, "Product").find_element_by_css_selector( + ".dash-cell-value > p" + ).get_attribute("innerHTML") == df[1].get("Sub-product") def test_tmcp004_copy_null_text_to_markdown(test): test.start_server(get_app()) - with test.table("table") as target: - target.cell.click(0, "Sub-product") + target = test.table("table") - test.copy() - target.cell.click(0, "Product") - test.paste() + target.cell.click(0, "Sub-product") - assert ( - target.cell.get(0, "Product") - .find_element_by_css_selector(".dash-cell-value > p") - .get_attribute("innerHTML") - == "null" - ) + test.copy() + target.cell.click(0, "Product") + test.paste() + + assert ( + target.cell.get(0, "Product") + .find_element_by_css_selector(".dash-cell-value > p") + .get_attribute("innerHTML") + == "null" + ) diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index 7903101c2..6f2c7cc17 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -60,79 +60,84 @@ def update_table(page_current, page_size): def test_tpag001_next_previous(test, mode): test.start_server(get_app(mode)) - with test.table("table") as target: - assert target.cell.get_text(0, 0) == "0" - assert target.paging.has_next_page() - assert not target.paging.has_prev_page() + target = test.table("table") - target.paging.click_next_page() + assert target.cell.get_text(0, 0) == "0" + assert target.paging.has_next_page() + assert not target.paging.has_prev_page() - assert target.cell.get_text(0, 0) == "5" - assert target.paging.has_next_page() - assert target.paging.has_prev_page() + target.paging.click_next_page() - target.paging.click_prev_page() + assert target.cell.get_text(0, 0) == "5" + assert target.paging.has_next_page() + assert target.paging.has_prev_page() - assert target.cell.get_text(0, 0) == "0" - assert target.paging.has_next_page() - assert not target.paging.has_prev_page() + target.paging.click_prev_page() + + assert target.cell.get_text(0, 0) == "0" + assert target.paging.has_next_page() + assert not target.paging.has_prev_page() @pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag002_ops_on_first_page(test, mode): test.start_server(get_app(mode)) - with test.table("table") as target: - assert target.paging.get_current_page() == "1" - assert not target.paging.has_first_page() - assert not target.paging.has_prev_page() - assert target.paging.has_next_page() - assert target.paging.has_last_page() + target = test.table("table") + + assert target.paging.get_current_page() == "1" + assert not target.paging.has_first_page() + assert not target.paging.has_prev_page() + assert target.paging.has_next_page() + assert target.paging.has_last_page() @pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag003_ops_on_last_page(test, mode): test.start_server(get_app(mode)) - with test.table("table") as target: - target.paging.click_last_page() + target = test.table("table") + + target.paging.click_last_page() - assert target.paging.get_current_page() == str(pages) - assert target.paging.has_first_page() - assert target.paging.has_prev_page() - assert not target.paging.has_next_page() - assert not target.paging.has_last_page() + assert target.paging.get_current_page() == str(pages) + assert target.paging.has_first_page() + assert target.paging.has_prev_page() + assert not target.paging.has_next_page() + assert not target.paging.has_last_page() def test_tpag004_ops_input_with_enter(test): test.start_server(get_app("native")) - with test.table("table") as target: - text00 = target.cell.get_text(0, 0) + target = test.table("table") - assert target.paging.get_current_page() == "1" + text00 = target.cell.get_text(0, 0) - target.paging.click_current_page() - test.send_keys("100" + Keys.ENTER) + assert target.paging.get_current_page() == "1" - assert target.paging.get_current_page() == "100" - assert target.cell.get_text(0, 0) != text00 + target.paging.click_current_page() + test.send_keys("100" + Keys.ENTER) + + assert target.paging.get_current_page() == "100" + assert target.cell.get_text(0, 0) != text00 def test_tpag005_ops_input_with_unfocus(test): test.start_server(get_app("native")) - with test.table("table") as target: - text00 = target.cell.get_text(0, 0) + target = test.table("table") + + text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == "1" + assert target.paging.get_current_page() == "1" - target.paging.click_current_page() - test.send_keys("100") - target.cell.click(0, 0) + target.paging.click_current_page() + test.send_keys("100") + target.cell.click(0, 0) - assert target.paging.get_current_page() == "100" - assert target.cell.get_text(0, 0) != text00 + assert target.paging.get_current_page() == "100" + assert target.cell.get_text(0, 0) != text00 @pytest.mark.parametrize( @@ -141,15 +146,16 @@ def test_tpag005_ops_input_with_unfocus(test): def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): test.start_server(get_app("native")) - with test.table("table") as target: - text00 = target.cell.get_text(0, 0) + target = test.table("table") - assert target.paging.get_current_page() == "1" + text00 = target.cell.get_text(0, 0) - target.paging.click_current_page() - test.send_keys(str(value) + Keys.ENTER) + assert target.paging.get_current_page() == "1" - assert target.paging.get_current_page() == str(expected_value) + target.paging.click_current_page() + test.send_keys(str(value) + Keys.ENTER) + + assert target.paging.get_current_page() == str(expected_value) @pytest.mark.parametrize( @@ -158,37 +164,41 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): test.start_server(get_app("native")) - with test.table("table") as target: - text00 = target.cell.get_text(0, 0) + target = test.table("table") + + text00 = target.cell.get_text(0, 0) - assert target.paging.get_current_page() == "1" + assert target.paging.get_current_page() == "1" - target.paging.click_current_page() - test.send_keys(str(value)) - target.cell.click(0, 0) + target.paging.click_current_page() + test.send_keys(str(value)) + target.cell.click(0, 0) - assert target.paging.get_current_page() == str(expected_value) + assert target.paging.get_current_page() == str(expected_value) @pytest.mark.parametrize("mode", ["custom", "native"]) def test_tpag008_hide_with_single_page(test, mode): test.start_server(get_app(mode=mode, data=df[0:PAGE_SIZE])) - with test.table("table") as target: - assert not target.paging.has_pagination() + target = test.table("table") + + assert not target.paging.has_pagination() def test_tpag009_hide_with_invalid_page_count(test): test.start_server(get_app(mode="custom", page_count=-1)) - with test.table("table") as target: - assert not target.paging.has_pagination() + target = test.table("table") + + assert not target.paging.has_pagination() def test_tpag010_limits_page(test): test.start_server(get_app(mode="custom", page_count=10)) - with test.table("table") as target: - target.paging.click_last_page() + target = test.table("table") + + target.paging.click_last_page() - assert target.paging.get_current_page() == "10" + assert target.paging.get_current_page() == "10" From 73092c7b929f4f2b2a3f997dd4bd47e4cc43f699 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 16:26:03 -0500 Subject: [PATCH 36/38] refactor table.cell, table.column, table.row to select target prior to action --- tests/selenium/conftest.py | 129 +++++++++---------- tests/selenium/test_basic_copy_paste.py | 93 ++++++++------ tests/selenium/test_basic_operations.py | 142 ++++++++++----------- tests/selenium/test_derived_props.py | 22 ++-- tests/selenium/test_editable.py | 34 ++--- tests/selenium/test_markdown_copy_paste.py | 28 ++-- tests/selenium/test_pagination.py | 22 ++-- 7 files changed, 244 insertions(+), 226 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 6f1d7a409..4939cf6ef 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -40,50 +40,48 @@ def __exit__(self, type, value, traceback): class DataTableCellFacade(object): - @preconditions(_validate_id, _validate_mixin) - def __init__(self, id, mixin): + @preconditions( + _validate_id, _validate_mixin, _validate_row, _validate_col, _validate_state + ) + def __init__(self, id, mixin, row, col, state=_ANY): self.id = id self.mixin = mixin + self.row = row + self.col = col + self.state = state - @preconditions(_validate_row, _validate_col, _validate_state) - def _get_cell_value(self, row, col, selector, state=_ANY): - return self.get(row, col, state).find_element_by_css_selector( - ".dash-cell-value" - ) + def _get_cell_value(self): + return self.get().find_element_by_css_selector(".dash-cell-value") - @preconditions(_validate_row, _validate_col, _validate_state) - def click(self, row, col, state=_ANY): - return self.get(row, col, state).click() + def click(self): + return self.get().click() - @preconditions(_validate_row, _validate_col, _validate_state) - def double_click(self, row, col, state=_ANY): + def double_click(self): ac = ActionChains(self.mixin.driver) - ac.move_to_element(self._get_cell_value(row, col, state)) + ac.move_to_element(self._get_cell_value()) ac.pause(1) # sometimes experiencing incorrect behavior on scroll otherwise ac.double_click() return ac.perform() - @preconditions(_validate_row, _validate_col, _validate_state) - def get(self, row, col, state=_ANY): - self.mixin._wait_for_table(self.id, state) + def get(self): + self.mixin._wait_for_table(self.id, self.state) return ( self.mixin.find_element( '#{} {} tbody td.dash-cell.column-{}[data-dash-row="{}"]'.format( - self.id, state, col, row + self.id, self.state, self.col, self.row ) ) - if isinstance(col, int) + if isinstance(self.col, int) else self.mixin.find_element( '#{} {} tbody td.dash-cell[data-dash-column="{}"][data-dash-row="{}"]'.format( - self.id, state, col, row + self.id, self.state, self.col, self.row ) ) ) - @preconditions(_validate_row, _validate_col, _validate_state) - def get_text(self, row, col, state=_ANY): - el = self._get_cell_value(row, col, state) + def get_text(self): + el = self._get_cell_value() value = el.get_attribute("value") return ( @@ -92,80 +90,74 @@ def get_text(self, row, col, state=_ANY): else el.get_attribute("innerHTML") ) - @preconditions(_validate_row, _validate_col, _validate_state) - def is_active(self, row, col, state=_ANY): - input = self.get(row, col, state).find_element_by_css_selector("input") + def is_active(self): + input = self.get().find_element_by_css_selector("input") return "focused" in input.get_attribute("class").split(" ") - @preconditions(_validate_row, _validate_col, _validate_state) - def is_focused(self, row, col, state=_ANY): - cell = self.get(row, col, state) + def is_focused(self): + cell = self.get() return "focused" in cell.get_attribute("class").split(" ") class DataTableColumnFacade(object): - @preconditions(_validate_id, _validate_mixin) - def __init__(self, id, mixin): + @preconditions(_validate_id, _validate_mixin, _validate_col_id, _validate_state) + def __init__(self, id, mixin, col_id, state=_ANY): self.id = id self.mixin = mixin + self.col_id = col_id + self.state = state - @preconditions(_validate_row, _validate_col_id, _validate_state) - def get(self, row, col_id, state=_ANY): - self.mixin._wait_for_table(self.id, state) + @preconditions(_validate_row) + def get(self, row): + self.mixin._wait_for_table(self.id, self.state) return self.mixin.find_elements( '#{} {} tbody tr th.dash-header[data-dash-column="{}"]'.format( - self.id, state, col_id + self.id, self.state, self.col_id ) )[row] - @preconditions(_validate_row, _validate_col_id, _validate_state) - def hide(self, row, col_id, state=_ANY): - self.get(row, col_id, state).find_element_by_css_selector( - ".column-header--hide" - ).click() + @preconditions(_validate_row) + def hide(self, row): + self.get(row).find_element_by_css_selector(".column-header--hide").click() - @preconditions(_validate_row, _validate_col_id, _validate_state) - def sort(self, row, col_id, state=_ANY): - self.get(row, col_id, state).find_element_by_css_selector( - ".column-header--sort" - ).click() + @preconditions(_validate_row) + def sort(self, row): + self.get(row).find_element_by_css_selector(".column-header--sort").click() - @preconditions(_validate_col_id, _validate_state) - def filter(self, col_id, state=_ANY): + def filter(self): return self.mixin.find_element( '#{} {} tbody tr th.dash-filter[data-dash-column="{}"]'.format( - self.id, state, col_id + self.id, self.state, self.col_id ) ).click() class DataTableRowFacade(object): - @preconditions(_validate_id, _validate_mixin) - def __init__(self, id, mixin): + @preconditions(_validate_id, _validate_mixin, _validate_row, _validate_state) + def __init__(self, id, mixin, row, state=_ANY): self.id = id self.mixin = mixin + self.row = row + self.state = state - @preconditions(_validate_row, _validate_state) - def delete(self, row, state=_ANY): + def delete(self): return self.mixin.find_elements( - "#{} {} tbody tr td.dash-delete-cell".format(self.id, state) - )[row].click() + "#{} {} tbody tr td.dash-delete-cell".format(self.id, self.state) + )[self.row].click() - @preconditions(_validate_row, _validate_state) - def select(self, row, state=_ANY): + def select(self): return self.mixin.find_elements( - "#{} {} tbody tr td.dash-select-cell".format(self.id, state) - )[row].click() + "#{} {} tbody tr td.dash-select-cell".format(self.id, self.state) + )[self.row].click() - @preconditions(_validate_row, _validate_state) - def is_selected(self, row, state=_ANY): + def is_selected(self): return ( self.mixin.find_elements( - "#{} {} tbody tr td.dash-select-cell".format(self.id, state) - )[row] + "#{} {} tbody tr td.dash-select-cell".format(self.id, self.state) + )[self.row] .find_element_by_css_selector("input") .is_selected() ) @@ -251,10 +243,19 @@ def __init__(self, id, mixin): self.id = id self.mixin = mixin - self.cell = DataTableCellFacade(id, mixin) - self.column = DataTableColumnFacade(id, mixin) self.paging = DataTablePagingFacade(id, mixin) - self.row = DataTableRowFacade(id, mixin) + + @preconditions(_validate_row, _validate_col, _validate_state) + def cell(self, row, col, state=_ANY): + return DataTableCellFacade(self.id, self.mixin, row, col, state) + + @preconditions(_validate_col_id, _validate_state) + def column(self, col_id, state=_ANY): + return DataTableColumnFacade(self.id, self.mixin, col_id, state) + + @preconditions(_validate_row, _validate_state) + def row(self, row, state=_ANY): + return DataTableRowFacade(self.id, self.mixin, row, state) def is_ready(self): return self.mixin._wait_for_table(self.id, _READY) diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index e8de4789e..f3f2a1113 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -76,41 +76,41 @@ def test_tbcp001_copy_paste_callback(test): test.start_server(get_app()) target = test.table("table") - target.cell.click(0, 0) + target.cell(0, 0).click() test.copy() - target.cell.click(1, 0) + target.cell(1, 0).click() test.paste() - assert target.cell.get_text(1, 0) == "0" - assert target.cell.get_text(1, 1) == "MODIFIED" + assert target.cell(1, 0).get_text() == "0" + assert target.cell(1, 1).get_text() == "MODIFIED" def test_tbcp002_sorted_copy_paste_callback(test): test.start_server(get_app()) target = test.table("table") - target.column.sort(0, rawDf.columns[2]) + target.column(rawDf.columns[2]).sort(0) - assert target.cell.get_text(0, 0) == "11" + assert target.cell(0, 0).get_text() == "11" - target.cell.click(0, 0) + target.cell(0, 0).click() test.copy() - target.cell.click(1, 0) + target.cell(1, 0).click() test.paste() - assert target.cell.get_text(1, 0) == "11" - assert target.cell.get_text(1, 1) == "MODIFIED" + assert target.cell(1, 0).get_text() == "11" + assert target.cell(1, 1).get_text() == "MODIFIED" - target.cell.click(1, 1) + target.cell(1, 1).click() test.copy() - target.cell.click(2, 1) + target.cell(2, 1).click() test.paste() - assert target.cell.get_text(1, 0) == "11" - assert target.cell.get_text(2, 1) == "MODIFIED" + assert target.cell(1, 0).get_text() == "11" + assert target.cell(2, 1).get_text() == "MODIFIED" def test_tbcp003_copy_multiple_rows(test): @@ -118,16 +118,16 @@ def test_tbcp003_copy_multiple_rows(test): target = test.table("table") with test.hold(Keys.SHIFT): - target.cell.click(0, 0) - target.cell.click(2, 0) + target.cell(0, 0).click() + target.cell(2, 0).click() test.copy() - target.cell.click(3, 0) + target.cell(3, 0).click() test.paste() for i in range(3): - assert target.cell.get_text(i + 3, 0) == target.cell.get_text(i, 0) - assert target.cell.get_text(i + 3, 1) == "MODIFIED" + assert target.cell(i + 3, 0).get_text() == target.cell(i, 0).get_text() + assert target.cell(i + 3, 1).get_text() == "MODIFIED" def test_tbcp004_copy_9_and_10(test): @@ -136,17 +136,19 @@ def test_tbcp004_copy_9_and_10(test): source = test.table("table") target = test.table("table2") - source.cell.click(9, 0) + source.cell(9, 0).click() with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).perform() test.copy() - target.cell.click(0, 0) + target.cell(0, 0).click() test.paste() for row in range(2): for col in range(1): - assert target.cell.get_text(row, col) == source.cell.get_text(row + 9, col) + assert ( + target.cell(row, col).get_text() == source.cell(row + 9, col).get_text() + ) def test_tbcp005_copy_multiple_rows_and_columns(test): @@ -154,17 +156,19 @@ def test_tbcp005_copy_multiple_rows_and_columns(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() with test.hold(Keys.SHIFT): - target.cell.click(2, 2) + target.cell(2, 2).click() test.copy() - target.cell.click(3, 1) + target.cell(3, 1).click() test.paste() for row in range(3): for col in range(1, 3): - assert target.cell.get_text(row + 3, col) == target.cell.get_text(row, col) + assert ( + target.cell(row + 3, col).get_text() == target.cell(row, col).get_text() + ) def test_tbcp006_copy_paste_between_tables(test): @@ -173,17 +177,20 @@ def test_tbcp006_copy_paste_between_tables(test): source = test.table("table") target = test.table("table2") - source.cell.click(10, 0) + source.cell(10, 0).click() with test.hold(Keys.SHIFT): - source.cell.click(13, 3) + source.cell(13, 3).click() test.copy() - target.cell.click(0, 0) + target.cell(0, 0).click() test.paste() for row in range(4): for col in range(4): - assert source.cell.get_text(row + 10, col) == target.cell.get_text(row, col) + assert ( + source.cell(row + 10, col).get_text() + == target.cell(row, col).get_text() + ) def test_tbcp007_copy_paste_with_hidden_column(test): @@ -191,19 +198,20 @@ def test_tbcp007_copy_paste_with_hidden_column(test): target = test.table("table") - target.column.hide(0, "Complaint ID") - target.cell.click(0, 0) + target.column("Complaint ID").hide(0) + target.cell(0, 0).click() with test.hold(Keys.SHIFT): - target.cell.click(2, 2) + target.cell(2, 2).click() test.copy() - target.cell.click(3, 1) + target.cell(3, 1).click() test.paste() for row in range(3): for col in range(3): - assert target.cell.get_text(row, col) == target.cell.get_text( - row + 3, col + 1 + assert ( + target.cell(row, col).get_text() + == target.cell(row + 3, col + 1).get_text() ) @@ -212,15 +220,18 @@ def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): target = test.table("table") - target.column.hide(0, "Complaint ID") - target.cell.click(10, 0) + target.column("Complaint ID").hide(0) + target.cell(10, 0).click() with test.hold(Keys.SHIFT): - target.cell.click(13, 2) + target.cell(13, 2).click() test.copy() - target.cell.click(0, 0) + target.cell(0, 0).click() test.paste() for row in range(4): for col in range(3): - assert target.cell.get_text(row + 10, col) == target.cell.get_text(row, col) + assert ( + target.cell(row + 10, col).get_text() + == target.cell(row, col).get_text() + ) diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index d881f9b35..3b3036988 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -38,9 +38,9 @@ def test_tbst001_get_cell(test): target = test.table("table") - assert target.cell.get_text(0, 0) == "0" + assert target.cell(0, 0).get_text() == "0" target.paging.click_next_page() - assert target.cell.get_text(0, 0) == "250" + assert target.cell(0, 0).get_text() == "250" def test_tbst002_select_all_text(test): @@ -48,9 +48,9 @@ def test_tbst002_select_all_text(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() - assert target.cell.get_text(0, 1) == test.get_selected_text() + assert target.cell(0, 1).get_text() == test.get_selected_text() # https://github.com/plotly/dash-table/issues/50 @@ -59,10 +59,10 @@ def test_tbst003_edit_on_enter(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc" + Keys.ENTER) - assert target.cell.get_text(249, 0) == "abc" + assert target.cell(249, 0).get_text() == "abc" # https://github.com/plotly/dash-table/issues/107 @@ -71,10 +71,10 @@ def test_tbst004_edit_on_tab(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc" + Keys.TAB) - assert target.cell.get_text(249, 0) == "abc" + assert target.cell(249, 0).get_text() == "abc" def test_tbst005_edit_last_row_on_click_outside(test): @@ -82,11 +82,11 @@ def test_tbst005_edit_last_row_on_click_outside(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc") - target.cell.click(248, 0) + target.cell(248, 0).click() - assert target.cell.get_text(249, 0) == "abc" + assert target.cell(249, 0).get_text() == "abc" # https://github.com/plotly/dash-table/issues/141 @@ -95,11 +95,11 @@ def test_tbst006_focused_arrow_left(test): target = test.table("table") - target.cell.click(249, 1) + target.cell(249, 1).click() test.send_keys("abc" + Keys.LEFT) - assert target.cell.get_text(249, 1) == "abc" - assert target.cell.is_focused(249, 0) + assert target.cell(249, 1).get_text() == "abc" + assert target.cell(249, 0).is_focused() # https://github.com/plotly/dash-table/issues/141 @@ -108,11 +108,11 @@ def test_tbst007_active_focused_arrow_right(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc" + Keys.RIGHT) - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(249, 1) + assert target.cell(249, 0).get_text() == "abc" + assert target.cell(249, 1).is_focused() # https://github.com/plotly/dash-table/issues/141 @@ -121,11 +121,11 @@ def test_tbst008_active_focused_arrow_up(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc" + Keys.UP) - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(248, 0) + assert target.cell(249, 0).get_text() == "abc" + assert target.cell(248, 0).is_focused() # https://github.com/plotly/dash-table/issues/141 @@ -134,11 +134,11 @@ def test_tbst009_active_focused_arrow_down(test): target = test.table("table") - target.cell.click(249, 0) + target.cell(249, 0).click() test.send_keys("abc" + Keys.DOWN) - assert target.cell.get_text(249, 0) == "abc" - assert target.cell.is_focused(249, 0) + assert target.cell(249, 0).get_text() == "abc" + assert target.cell(249, 0).is_focused() def test_tbst010_active_with_dblclick(test): @@ -146,9 +146,9 @@ def test_tbst010_active_with_dblclick(test): target = test.table("table") - target.cell.double_click(0, 0) - assert target.cell.is_active(0, 0) - assert target.cell.get_text(0, 0) == test.get_selected_text() + target.cell(0, 0).double_click() + assert target.cell(0, 0).is_active() + assert target.cell(0, 0).get_text() == test.get_selected_text() def test_tbst011_delete_row(test): @@ -156,10 +156,10 @@ def test_tbst011_delete_row(test): target = test.table("table") - text01 = target.cell.get_text(1, 0) - target.row.delete(0) + text01 = target.cell(1, 0).get_text() + target.row(0).delete() - assert target.cell.get_text(0, 0) == text01 + assert target.cell(0, 0).get_text() == text01 def test_tbst012_delete_sorted_row(test): @@ -167,13 +167,13 @@ def test_tbst012_delete_sorted_row(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - text01 = target.cell.get_text(1, 0) - target.row.delete(0) + text01 = target.cell(1, 0).get_text() + target.row(0).delete() - assert target.cell.get_text(0, 0) == text01 + assert target.cell(0, 0).get_text() == text01 def test_tbst013_select_row(test): @@ -181,9 +181,9 @@ def test_tbst013_select_row(test): target = test.table("table") - target.row.select(0) + target.row(0).select() - assert target.row.is_selected(0) + assert target.row(0).is_selected() def test_tbst014_selected_sorted_row(test): @@ -191,11 +191,11 @@ def test_tbst014_selected_sorted_row(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC - target.row.select(0) + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.row(0).select() - assert target.row.is_selected(0) + assert target.row(0).is_selected() def test_tbst015_selected_row_respects_sort(test): @@ -203,18 +203,18 @@ def test_tbst015_selected_row_respects_sort(test): target = test.table("table") - target.row.select(0) + target.row(0).select() - assert target.row.is_selected(0) + assert target.row(0).is_selected() - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - assert not target.row.is_selected(0) + assert not target.row(0).is_selected() - target.column.sort(0, rawDf.columns[0]) # DESC -> None + target.column(rawDf.columns[0]).sort(0) # DESC -> None - assert target.row.is_selected(0) + assert target.row(0).is_selected() def test_tbst016_delete_cell(test): @@ -222,11 +222,11 @@ def test_tbst016_delete_cell(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) test.send_keys(Keys.ENTER) - assert target.cell.get_text(0, 1) == "" + assert target.cell(0, 1).get_text() == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -235,10 +235,10 @@ def test_tbst017_delete_cell_updates_while_selected(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == "" + assert target.cell(0, 1).get_text() == "" def test_tbst018_delete_multiple_cells(test): @@ -246,7 +246,7 @@ def test_tbst018_delete_multiple_cells(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() @@ -254,7 +254,7 @@ def test_tbst018_delete_multiple_cells(test): for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == "" + assert target.cell(row, col).get_text() == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -263,7 +263,7 @@ def test_tbst019_delete_multiple_cells_while_selected(test): target = test.table("table") - target.cell.click(0, 1) + target.cell(0, 1).click() with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() @@ -271,7 +271,7 @@ def test_tbst019_delete_multiple_cells_while_selected(test): for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == "" + assert target.cell(row, col).get_text() == "" def test_tbst020_sorted_table_delete_cell(test): @@ -279,14 +279,14 @@ def test_tbst020_sorted_table_delete_cell(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - target.cell.click(0, 1) + target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) test.send_keys(Keys.ENTER) - assert target.cell.get_text(0, 1) == "" + assert target.cell(0, 1).get_text() == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -295,13 +295,13 @@ def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - target.cell.click(0, 1) + target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) - assert target.cell.get_text(0, 1) == "" + assert target.cell(0, 1).get_text() == "" def test_tbst022_sorted_table_delete_multiple_cells(test): @@ -309,10 +309,10 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - target.cell.click(0, 1) + target.cell(0, 1).click() with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() @@ -320,7 +320,7 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == "" + assert target.cell(row, col).get_text() == "" @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -329,10 +329,10 @@ def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): target = test.table("table") - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC - target.cell.click(0, 1) + target.cell(0, 1).click() with test.hold(Keys.SHIFT): ActionChains(test.driver).send_keys(Keys.DOWN).send_keys(Keys.RIGHT).perform() @@ -340,4 +340,4 @@ def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): for row in range(2): for col in range(1, 3): - assert target.cell.get_text(row, col) == "" + assert target.cell(row, col).get_text() == "" diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py index 4eb5d43bc..4786f859b 100644 --- a/tests/selenium/test_derived_props.py +++ b/tests/selenium/test_derived_props.py @@ -84,8 +84,8 @@ def test_tdrp001_select_rows(test): target = test.table("table") - target.row.select(0) - target.row.select(1) + target.row(0).select() + target.row(1).select() assert test.find_element("#active_cell").get_attribute("innerHTML") in [ "None", @@ -142,7 +142,7 @@ def test_tdrp002_select_cell(test): target = test.table("table") - target.cell.click(0, 0) + target.cell(0, 0).click() active = dict(row=0, column=0, column_id=rawDf.columns[0], row_id=3000) @@ -199,7 +199,7 @@ def test_tdrp003_select_cells(test): target = test.table("table") - target.cell.click(0, 0) + target.cell(0, 0).click() with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) @@ -333,7 +333,7 @@ def test_tdrp004_navigate_selected_cells(test): target = test.table("table") - target.cell.click(0, 0) + target.cell(0, 0).click() with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.DOWN + Keys.RIGHT + Keys.RIGHT) @@ -409,9 +409,9 @@ def test_tdrp005_filtered_and_sorted_row_select(test): target = test.table("table") - target.row.select(0) - target.row.select(1) - target.row.select(2) + target.row(0).select() + target.row(1).select() + target.row(2).select() assert test.find_element("#active_cell").get_attribute("innerHTML") in [ "None", @@ -462,7 +462,7 @@ def test_tdrp005_filtered_and_sorted_row_select(test): "innerHTML" ) == json.dumps(list(range(3000, 3100))) - target.column.filter(rawDf.columns[0]) + target.column(rawDf.columns[0]).filter() test.send_keys("is even" + Keys.ENTER) assert test.find_element("#active_cell").get_attribute("innerHTML") in [ @@ -514,8 +514,8 @@ def test_tdrp005_filtered_and_sorted_row_select(test): "innerHTML" ) == json.dumps(list(range(3000, 3100, 2))) - target.column.sort(0, rawDf.columns[0]) # None -> ASC - target.column.sort(0, rawDf.columns[0]) # ASC -> DESC + target.column(rawDf.columns[0]).sort(0) # None -> ASC + target.column(rawDf.columns[0]).sort(0) # ASC -> DESC assert test.find_element("#active_cell").get_attribute("innerHTML") in [ "None", diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py index 29508c03e..b25b40a76 100644 --- a/tests/selenium/test_editable.py +++ b/tests/selenium/test_editable.py @@ -74,11 +74,11 @@ def test_tedi001_loading_on_data_change(test): with blocking: test.find_element("#blocking").click() target.is_loading() - target.cell.click(0, 0) - assert len(target.cell.get(0, 0).find_elements_by_css_selector("input")) == 0 + target.cell(0, 0).click() + assert len(target.cell(0, 0).get().find_elements_by_css_selector("input")) == 0 target.is_ready() - assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + assert target.cell(0, 0).get().find_element_by_css_selector("input") is not None def test_tedi002_ready_on_non_data_change(test): @@ -91,11 +91,11 @@ def test_tedi002_ready_on_non_data_change(test): with blocking: test.find_element("#non-blocking").click() target.is_ready() - target.cell.click(0, 0) - assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + target.cell(0, 0).click() + assert target.cell(0, 0).get().find_element_by_css_selector("input") is not None target.is_ready() - assert target.cell.get(0, 0).find_element_by_css_selector("input") is not None + assert target.cell(0, 0).get().find_element_by_css_selector("input") is not None def test_tedi003_does_not_steal_focus(test): @@ -123,9 +123,9 @@ def test_tedi004_edit_on_non_blocking(test): with blocking: test.find_element("#non-blocking").click() - target.cell.click(0, 0) + target.cell(0, 0).click() test.send_keys("abc" + Keys.ENTER) - assert target.cell.get_text(0, 0) == "abc" + assert target.cell(0, 0).get_text() == "abc" def test_tedi005_prevent_copy_paste_on_blocking(test): @@ -137,18 +137,19 @@ def test_tedi005_prevent_copy_paste_on_blocking(test): with blocking: test.find_element("#blocking").click() - target.cell.click(0, 0) + target.cell(0, 0).click() with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.RIGHT) test.copy() - target.cell.click(2, 0) + target.cell(2, 0).click() test.paste() for row in range(2): for col in range(2): - assert target.cell.get_text(row + 2, col) != target.cell.get_text( - row, col + assert ( + target.cell(row + 2, col).get_text() + != target.cell(row, col).get_text() ) @@ -161,16 +162,17 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): with non_blocking: test.find_element("#non-blocking").click() - target.cell.click(0, 0) + target.cell(0, 0).click() with test.hold(Keys.SHIFT): test.send_keys(Keys.DOWN + Keys.RIGHT) test.copy() - target.cell.click(2, 0) + target.cell(2, 0).click() test.paste() for row in range(2): for col in range(2): - assert target.cell.get_text(row + 2, col) == target.cell.get_text( - row, col + assert ( + target.cell(row + 2, col).get_text() + == target.cell(row, col).get_text() ) diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index f882e9c46..1bcab4443 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -43,13 +43,13 @@ def test_tmcp001_copy_markdown_to_text(test): target = test.table("table") - target.cell.click(0, "Issue") + target.cell(0, "Issue").click() test.copy() - target.cell.click(0, "Sub-product") + target.cell(0, "Sub-product").click() test.paste() - assert target.cell.get_text(0, 2) == df[0].get("Issue") + assert target.cell(0, 2).get_text() == df[0].get("Issue") def test_tmcp002_copy_markdown_to_markdown(test): @@ -57,13 +57,16 @@ def test_tmcp002_copy_markdown_to_markdown(test): target = test.table("table") - target.cell.click(0, "Product") + target.cell(0, "Product").click() test.copy() - target.cell.click(0, "Complaint ID") + target.cell(0, "Complaint ID").click() test.paste() - assert target.cell.get_text(0, "Complaint ID") == target.cell.get_text(0, "Product") + assert ( + target.cell(0, "Complaint ID").get_text() + == target.cell(0, "Product").get_text() + ) def test_tmcp003_copy_text_to_markdown(test): @@ -71,13 +74,13 @@ def test_tmcp003_copy_text_to_markdown(test): target = test.table("table") - target.cell.click(1, "Sub-product") + target.cell(1, "Sub-product").click() test.copy() - target.cell.click(1, "Product") + target.cell(1, "Product").click() test.paste() - assert target.cell.get(1, "Product").find_element_by_css_selector( + assert target.cell(1, "Product").get().find_element_by_css_selector( ".dash-cell-value > p" ).get_attribute("innerHTML") == df[1].get("Sub-product") @@ -87,14 +90,15 @@ def test_tmcp004_copy_null_text_to_markdown(test): target = test.table("table") - target.cell.click(0, "Sub-product") + target.cell(0, "Sub-product").click() test.copy() - target.cell.click(0, "Product") + target.cell(0, "Product").click() test.paste() assert ( - target.cell.get(0, "Product") + target.cell(0, "Product") + .get() .find_element_by_css_selector(".dash-cell-value > p") .get_attribute("innerHTML") == "null" diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index 6f2c7cc17..7dc4717a0 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -62,19 +62,19 @@ def test_tpag001_next_previous(test, mode): target = test.table("table") - assert target.cell.get_text(0, 0) == "0" + assert target.cell(0, 0).get_text() == "0" assert target.paging.has_next_page() assert not target.paging.has_prev_page() target.paging.click_next_page() - assert target.cell.get_text(0, 0) == "5" + assert target.cell(0, 0).get_text() == "5" assert target.paging.has_next_page() assert target.paging.has_prev_page() target.paging.click_prev_page() - assert target.cell.get_text(0, 0) == "0" + assert target.cell(0, 0).get_text() == "0" assert target.paging.has_next_page() assert not target.paging.has_prev_page() @@ -112,7 +112,7 @@ def test_tpag004_ops_input_with_enter(test): target = test.table("table") - text00 = target.cell.get_text(0, 0) + text00 = target.cell(0, 0).get_text() assert target.paging.get_current_page() == "1" @@ -120,7 +120,7 @@ def test_tpag004_ops_input_with_enter(test): test.send_keys("100" + Keys.ENTER) assert target.paging.get_current_page() == "100" - assert target.cell.get_text(0, 0) != text00 + assert target.cell(0, 0).get_text() != text00 def test_tpag005_ops_input_with_unfocus(test): @@ -128,16 +128,16 @@ def test_tpag005_ops_input_with_unfocus(test): target = test.table("table") - text00 = target.cell.get_text(0, 0) + text00 = target.cell(0, 0).get_text() assert target.paging.get_current_page() == "1" target.paging.click_current_page() test.send_keys("100") - target.cell.click(0, 0) + target.cell(0, 0).click() assert target.paging.get_current_page() == "100" - assert target.cell.get_text(0, 0) != text00 + assert target.cell(0, 0).get_text() != text00 @pytest.mark.parametrize( @@ -148,7 +148,7 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): target = test.table("table") - text00 = target.cell.get_text(0, 0) + text00 = target.cell(0, 0).get_text() assert target.paging.get_current_page() == "1" @@ -166,13 +166,13 @@ def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): target = test.table("table") - text00 = target.cell.get_text(0, 0) + text00 = target.cell(0, 0).get_text() assert target.paging.get_current_page() == "1" target.paging.click_current_page() test.send_keys(str(value)) - target.cell.click(0, 0) + target.cell(0, 0).click() assert target.paging.get_current_page() == str(expected_value) From fb13a423640275c9b25f17ec5c1372c8d75acc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 16:44:19 -0500 Subject: [PATCH 37/38] streamline column usage (row=0) --- tests/selenium/conftest.py | 6 ++--- tests/selenium/test_basic_copy_paste.py | 6 ++--- tests/selenium/test_basic_operations.py | 30 ++++++++++++------------- tests/selenium/test_derived_props.py | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 4939cf6ef..dc17c37f4 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -110,7 +110,7 @@ def __init__(self, id, mixin, col_id, state=_ANY): self.state = state @preconditions(_validate_row) - def get(self, row): + def get(self, row=0): self.mixin._wait_for_table(self.id, self.state) return self.mixin.find_elements( @@ -120,11 +120,11 @@ def get(self, row): )[row] @preconditions(_validate_row) - def hide(self, row): + def hide(self, row=0): self.get(row).find_element_by_css_selector(".column-header--hide").click() @preconditions(_validate_row) - def sort(self, row): + def sort(self, row=0): self.get(row).find_element_by_css_selector(".column-header--sort").click() def filter(self): diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index f3f2a1113..c35cb1d07 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -90,7 +90,7 @@ def test_tbcp002_sorted_copy_paste_callback(test): test.start_server(get_app()) target = test.table("table") - target.column(rawDf.columns[2]).sort(0) + target.column(rawDf.columns[2]).sort() assert target.cell(0, 0).get_text() == "11" @@ -198,7 +198,7 @@ def test_tbcp007_copy_paste_with_hidden_column(test): target = test.table("table") - target.column("Complaint ID").hide(0) + target.column("Complaint ID").hide() target.cell(0, 0).click() with test.hold(Keys.SHIFT): target.cell(2, 2).click() @@ -220,7 +220,7 @@ def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): target = test.table("table") - target.column("Complaint ID").hide(0) + target.column("Complaint ID").hide() target.cell(10, 0).click() with test.hold(Keys.SHIFT): target.cell(13, 2).click() diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 3b3036988..26f5ded48 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -167,8 +167,8 @@ def test_tbst012_delete_sorted_row(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC text01 = target.cell(1, 0).get_text() target.row(0).delete() @@ -191,8 +191,8 @@ def test_tbst014_selected_sorted_row(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC target.row(0).select() assert target.row(0).is_selected() @@ -207,12 +207,12 @@ def test_tbst015_selected_row_respects_sort(test): assert target.row(0).is_selected() - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC assert not target.row(0).is_selected() - target.column(rawDf.columns[0]).sort(0) # DESC -> None + target.column(rawDf.columns[0]).sort() # DESC -> None assert target.row(0).is_selected() @@ -279,8 +279,8 @@ def test_tbst020_sorted_table_delete_cell(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) @@ -295,8 +295,8 @@ def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC target.cell(0, 1).click() test.send_keys(Keys.BACKSPACE) @@ -309,8 +309,8 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC target.cell(0, 1).click() with test.hold(Keys.SHIFT): @@ -329,8 +329,8 @@ def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): target = test.table("table") - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC target.cell(0, 1).click() with test.hold(Keys.SHIFT): diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py index 4786f859b..4b8465449 100644 --- a/tests/selenium/test_derived_props.py +++ b/tests/selenium/test_derived_props.py @@ -514,8 +514,8 @@ def test_tdrp005_filtered_and_sorted_row_select(test): "innerHTML" ) == json.dumps(list(range(3000, 3100, 2))) - target.column(rawDf.columns[0]).sort(0) # None -> ASC - target.column(rawDf.columns[0]).sort(0) # ASC -> DESC + target.column(rawDf.columns[0]).sort() # None -> ASC + target.column(rawDf.columns[0]).sort() # ASC -> DESC assert test.find_element("#active_cell").get_attribute("innerHTML") in [ "None", From 534c7b277f208aeda1fc9362420ee1f170b94197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andre=CC=81=20Rivet?= Date: Fri, 28 Feb 2020 19:50:27 -0500 Subject: [PATCH 38/38] refactor paging facade --- tests/selenium/conftest.py | 84 +++++++++++-------------- tests/selenium/test_basic_operations.py | 2 +- tests/selenium/test_pagination.py | 70 ++++++++++----------- 3 files changed, 72 insertions(+), 84 deletions(-) diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index dc17c37f4..6d0d29f15 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -17,6 +17,7 @@ _validate_keys = lambda keys: isinstance(keys, str) and len(keys) > 0 _validate_mixin = lambda mixin: isinstance(mixin, DataTableMixin) _validate_row = lambda row: isinstance(row, int) and row >= 0 +_validate_selector = lambda selector: isinstance(selector, str) and len(selector) > 0 _validate_state = lambda state: state in [_READY, _LOADING, _ANY] _validate_target = lambda target: isinstance(target, DataTableFacade) @@ -163,78 +164,65 @@ def is_selected(self): ) -class DataTablePagingFacade(object): - @preconditions(_validate_id, _validate_mixin) - def __init__(self, id, mixin): +class DataTablePagingActionFacade(object): + @preconditions(_validate_id, _validate_mixin, _validate_selector) + def __init__(self, id, mixin, selector): self.id = id self.mixin = mixin + self.selector = selector - def click_next_page(self): - self.mixin._wait_for_table(self.id) - - return self.mixin.find_element("#{} button.next-page".format(self.id)).click() - - def click_prev_page(self): - self.mixin._wait_for_table(self.id) - - return self.mixin.find_element( - "#{} button.previous-page".format(self.id) - ).click() - - def click_first_page(self): - self.mixin._wait_for_table(self.id) - - return self.mixin.find_element("#{} button.first-page".format(self.id)).click() - - def click_last_page(self): - self.mixin._wait_for_table(self.id) - - return self.mixin.find_element("#{} button.last-page".format(self.id)).click() - - def has_pagination(self): + def click(self): self.mixin._wait_for_table(self.id) - return len(self.mixin.find_elements(".previous-next-container")) != 0 + return self.mixin.find_element("#{} {}".format(self.id, self.selector)).click() - def has_next_page(self): + def exists(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element("#{} button.next-page".format(self.id)) + el = self.mixin.find_element("#{} {}".format(self.id, self.selector)) return el is not None and el.is_enabled() - def has_prev_page(self): - self.mixin._wait_for_table(self.id) - - el = self.mixin.find_element("#{} button.previous-page".format(self.id)) - return el is not None and el.is_enabled() +class DataTablePagingCurrentFacade(object): + @preconditions(_validate_id, _validate_mixin) + def __init__(self, id, mixin): + self.id = id + self.mixin = mixin - def has_first_page(self): + def click(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element("#{} button.first-page".format(self.id)) - - return el is not None and el.is_enabled() + return self.mixin.find_element("#{} input.current-page".format(self.id)).click() - def has_last_page(self): + def get_value(self): self.mixin._wait_for_table(self.id) - el = self.mixin.find_element("#{} button.last-page".format(self.id)) + return self.mixin.find_element( + "#{} input.current-page".format(self.id) + ).get_attribute("placeholder") - return el is not None and el.is_enabled() - def click_current_page(self): - self.mixin._wait_for_table(self.id) +class DataTablePagingFacade(object): + @preconditions(_validate_id, _validate_mixin) + def __init__(self, id, mixin): + self.id = id + self.mixin = mixin - return self.mixin.find_element("#{} input.current-page".format(self.id)).click() + self.current = DataTablePagingCurrentFacade(self.id, self.mixin) + self.first = DataTablePagingActionFacade( + self.id, self.mixin, "button.first-page" + ) + self.last = DataTablePagingActionFacade(self.id, self.mixin, "button.last-page") + self.next = DataTablePagingActionFacade(self.id, self.mixin, "button.next-page") + self.previous = DataTablePagingActionFacade( + self.id, self.mixin, "button.previous-page" + ) - def get_current_page(self): + def exists(self): self.mixin._wait_for_table(self.id) - return self.mixin.find_element( - "#{} input.current-page".format(self.id) - ).get_attribute("placeholder") + return len(self.mixin.find_elements(".previous-next-container")) != 0 class DataTableFacade(object): diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index 26f5ded48..ed51645c1 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -39,7 +39,7 @@ def test_tbst001_get_cell(test): target = test.table("table") assert target.cell(0, 0).get_text() == "0" - target.paging.click_next_page() + target.paging.next.click() assert target.cell(0, 0).get_text() == "250" diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index 7dc4717a0..3d4c046b4 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -63,20 +63,20 @@ def test_tpag001_next_previous(test, mode): target = test.table("table") assert target.cell(0, 0).get_text() == "0" - assert target.paging.has_next_page() - assert not target.paging.has_prev_page() + assert target.paging.next.exists() + assert not target.paging.previous.exists() - target.paging.click_next_page() + target.paging.next.click() assert target.cell(0, 0).get_text() == "5" - assert target.paging.has_next_page() - assert target.paging.has_prev_page() + assert target.paging.next.exists() + assert target.paging.previous.exists() - target.paging.click_prev_page() + target.paging.previous.click() assert target.cell(0, 0).get_text() == "0" - assert target.paging.has_next_page() - assert not target.paging.has_prev_page() + assert target.paging.next.exists() + assert not target.paging.previous.exists() @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -85,11 +85,11 @@ def test_tpag002_ops_on_first_page(test, mode): target = test.table("table") - assert target.paging.get_current_page() == "1" - assert not target.paging.has_first_page() - assert not target.paging.has_prev_page() - assert target.paging.has_next_page() - assert target.paging.has_last_page() + assert target.paging.current.get_value() == "1" + assert not target.paging.first.exists() + assert not target.paging.previous.exists() + assert target.paging.next.exists() + assert target.paging.last.exists() @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -98,13 +98,13 @@ def test_tpag003_ops_on_last_page(test, mode): target = test.table("table") - target.paging.click_last_page() + target.paging.last.click() - assert target.paging.get_current_page() == str(pages) - assert target.paging.has_first_page() - assert target.paging.has_prev_page() - assert not target.paging.has_next_page() - assert not target.paging.has_last_page() + assert target.paging.current.get_value() == str(pages) + assert target.paging.first.exists() + assert target.paging.previous.exists() + assert not target.paging.next.exists() + assert not target.paging.last.exists() def test_tpag004_ops_input_with_enter(test): @@ -114,12 +114,12 @@ def test_tpag004_ops_input_with_enter(test): text00 = target.cell(0, 0).get_text() - assert target.paging.get_current_page() == "1" + assert target.paging.current.get_value() == "1" - target.paging.click_current_page() + target.paging.current.click() test.send_keys("100" + Keys.ENTER) - assert target.paging.get_current_page() == "100" + assert target.paging.current.get_value() == "100" assert target.cell(0, 0).get_text() != text00 @@ -130,13 +130,13 @@ def test_tpag005_ops_input_with_unfocus(test): text00 = target.cell(0, 0).get_text() - assert target.paging.get_current_page() == "1" + assert target.paging.current.get_value() == "1" - target.paging.click_current_page() + target.paging.current.click() test.send_keys("100") target.cell(0, 0).click() - assert target.paging.get_current_page() == "100" + assert target.paging.current.get_value() == "100" assert target.cell(0, 0).get_text() != text00 @@ -150,12 +150,12 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): text00 = target.cell(0, 0).get_text() - assert target.paging.get_current_page() == "1" + assert target.paging.current.get_value() == "1" - target.paging.click_current_page() + target.paging.current.click() test.send_keys(str(value) + Keys.ENTER) - assert target.paging.get_current_page() == str(expected_value) + assert target.paging.current.get_value() == str(expected_value) @pytest.mark.parametrize( @@ -168,13 +168,13 @@ def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): text00 = target.cell(0, 0).get_text() - assert target.paging.get_current_page() == "1" + assert target.paging.current.get_value() == "1" - target.paging.click_current_page() + target.paging.current.click() test.send_keys(str(value)) target.cell(0, 0).click() - assert target.paging.get_current_page() == str(expected_value) + assert target.paging.current.get_value() == str(expected_value) @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -183,7 +183,7 @@ def test_tpag008_hide_with_single_page(test, mode): target = test.table("table") - assert not target.paging.has_pagination() + assert not target.paging.exists() def test_tpag009_hide_with_invalid_page_count(test): @@ -191,7 +191,7 @@ def test_tpag009_hide_with_invalid_page_count(test): target = test.table("table") - assert not target.paging.has_pagination() + assert not target.paging.exists() def test_tpag010_limits_page(test): @@ -199,6 +199,6 @@ def test_tpag010_limits_page(test): target = test.table("table") - target.paging.click_last_page() + target.paging.last.click() - assert target.paging.get_current_page() == "10" + assert target.paging.current.get_value() == "10"