diff --git a/.circleci/config.yml b/.circleci/config.yml index 78b0681b2..803e5ea88 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -183,68 +183,10 @@ jobs: npm run lint when: always - - "python-3.6": - docker: - - image: circleci/python:3.7.5-stretch-node-browsers - - environment: - PERCY_ENABLED: True - PERCY_PROJECT: plotly/dash-table-python-v0 - - steps: - - checkout - - - run: - name: Inject Percy Environment variables - command: | - echo 'export PERCY_TOKEN="$PERCY_PYTHON_TOKEN_V0"' >> $BASH_ENV - - - 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 - npm ci - - - run: - name: Install dependencies (dash) - command: | - . venv/bin/activate - 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 ci && npm run build && pip install -e . && cd ../.. - - - run: - name: Install test requirements - command: | - . venv/bin/activate - npm run build - python setup.py sdist - cd dist - find . -name "*.gz" | xargs pip install --no-cache-dir --ignore-installed && cd .. - - - run: - name: ⚙️ run integration test - command: | - . venv/bin/activate - pytest --junitxml=test-reports/dash.xml tests/integration - - store_artifacts: - path: test-reports - - store_test_results: - path: test-reports - - store_artifacts: - path: /tmp/dash_artifacts - - - workflows: version: 2 build: jobs: - - "python-3.6" - "node" - "server-test" - "standalone-test" diff --git a/CHANGELOG.md b/CHANGELOG.md index eab5965ec..bab596389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Fixed +- [#841](https://github.com/plotly/dash-table/pull/841) + - Fix prop-types regression causing console errors in browser devtools + - Fix syntax highlighting regression for Markdown cells + +### Added +- [#841](https://github.com/plotly/dash-table/pull/841) Add Julia syntax highlighting support for Markdown cells + ## [4.10.1] - 2020-09-03 -Dash.jl Julia component generation diff --git a/package-lock.json b/package-lock.json index 46e47939b..a24036eb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14317,9 +14317,9 @@ "dev": true }, "highlight.js": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.1.2.tgz", - "integrity": "sha512-Q39v/Mn5mfBlMff9r+zzA+gWxRsCRKwEMvYTiisLr/XUiFI/4puWt0Ojdko3R3JCNWGdOWaA5g/Yxqa23kC5AA==", + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.3.1.tgz", + "integrity": "sha512-jeW8rdPdhshYKObedYg5XGbpVgb1/DT4AHvDFXhkU7UnGSIjy9kkJ7zHG7qplhFHMitTSzh5/iClKQk3Kb2RFQ==", "dev": true }, "hmac-drbg": { diff --git a/package.json b/package.json index bab3cfb20..5160cc330 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "es-check": "^5.1.0", "fast-isnumeric": "^1.1.4", "file-loader": "^6.0.0", - "highlight.js": "^10.1.2", + "highlight.js": "^10.3.1", "http-server": "^0.12.3", "husky": "^4.2.5", "less": "^3.12.2", diff --git a/src/dash-table/dash/DataTable.js b/src/dash-table/dash/DataTable.js index bf52897b2..eee55756f 100644 --- a/src/dash-table/dash/DataTable.js +++ b/src/dash-table/dash/DataTable.js @@ -996,7 +996,7 @@ export const propTypes = { }), PropTypes.arrayOf( PropTypes.oneOfType([ - PropTypes.nully, + PropTypes.oneOf([null]), PropTypes.string, PropTypes.exact({ delay: PropTypes.number, diff --git a/src/third-party/highlight.js b/src/third-party/highlight.js index 7dcbb48ad..30d072ffa 100644 --- a/src/third-party/highlight.js +++ b/src/third-party/highlight.js @@ -1,4 +1,4 @@ -import highlightjs from 'highlight.js/lib/highlight'; +import highlightjs from 'highlight.js/lib/core'; import 'highlight.js/styles/github.css'; import bash from 'highlight.js/lib/languages/bash'; @@ -6,7 +6,9 @@ import css from 'highlight.js/lib/languages/css'; import http from 'highlight.js/lib/languages/http'; import javascript from 'highlight.js/lib/languages/javascript'; import json from 'highlight.js/lib/languages/json'; +import julia from 'highlight.js/lib/languages/julia'; import markdown from 'highlight.js/lib/languages/markdown'; +import plaintext from 'highlight.js/lib/languages/plaintext'; import python from 'highlight.js/lib/languages/python'; import r from 'highlight.js/lib/languages/r'; import ruby from 'highlight.js/lib/languages/ruby'; @@ -20,7 +22,9 @@ highlightjs.registerLanguage('css', css); highlightjs.registerLanguage('http', http); highlightjs.registerLanguage('javascript', javascript); highlightjs.registerLanguage('json', json); +highlightjs.registerLanguage('julia', julia); highlightjs.registerLanguage('markdown', markdown); +highlightjs.registerLanguage('plaintext', plaintext); highlightjs.registerLanguage('python', python); highlightjs.registerLanguage('r', r); highlightjs.registerLanguage('ruby', ruby); diff --git a/tests/integration/IntegrationTests.py b/tests/integration/IntegrationTests.py deleted file mode 100644 index da90f9263..000000000 --- a/tests/integration/IntegrationTests.py +++ /dev/null @@ -1,91 +0,0 @@ -from __future__ import absolute_import -import os -import multiprocessing -import time -import unittest -import percy -import threading -import platform -import flask -import requests - -from selenium import webdriver -from selenium.webdriver.chrome.options import Options - - -class IntegrationTests(unittest.TestCase): - @classmethod - def setUpClass(cls): - super(IntegrationTests, cls).setUpClass() - - options = Options() - - if "DASH_TEST_CHROMEPATH" in os.environ: - options.binary_location = os.environ["DASH_TEST_CHROMEPATH"] - - cls.driver = webdriver.Chrome(chrome_options=options) - loader = percy.ResourceLoader(webdriver=cls.driver) - cls.percy_runner = percy.Runner(loader=loader) - cls.percy_runner.initialize_build() - - @classmethod - def tearDownClass(cls): - super(IntegrationTests, cls).tearDownClass() - cls.driver.quit() - cls.percy_runner.finalize_build() - - def setUp(self): - pass - - def tearDown(self): - time.sleep(3) - if platform.system() == "Windows": - requests.get("http://localhost:8050/stop") - else: - self.server_process.terminate() - self.driver.back() - time.sleep(3) - - def snapshot(self, name): - if "PERCY_PROJECT" in os.environ and "PERCY_TOKEN" in os.environ: - self.percy_runner.snapshot(name=name) - - def startServer(self, app): - """ - - :param app: - :type app: dash.Dash - :return: - """ - if "DASH_TEST_PROCESSES" in os.environ: - processes = int(os.environ["DASH_TEST_PROCESSES"]) - else: - processes = 4 - - def run(): - app.run_server(port=8050, debug=False, processes=processes, threaded=False) - - def run_windows(): - @app.server.route("/stop") - def _stop_server_windows(): - stopper = flask.request.environ["werkzeug.server.shutdown"] - stopper() - return "stop" - - app.run_server(port=8050, debug=False, threaded=True) - - # Run on a separate process so that it doesn't block - - system = platform.system() - if system == "Windows": - # multiprocess can't pickle an inner func on windows (closure are not serializable by default on windows) - self.server_thread = threading.Thread(target=run_windows) - self.server_thread.start() - else: - self.server_process = multiprocessing.Process(target=run) - self.server_process.start() - time.sleep(5) - - # Visit the dash page - self.driver.get("http://localhost:8050") - self.driver.implicitly_wait(2) diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/integration/review_app/test_app_df_backend_paging.py b/tests/integration/review_app/test_app_df_backend_paging.py deleted file mode 100644 index 0f3118684..000000000 --- a/tests/integration/review_app/test_app_df_backend_paging.py +++ /dev/null @@ -1,399 +0,0 @@ -import os -from textwrap import dedent -import pandas as pd -import dash -from dash.dependencies import Input, Output -import dash_core_components as dcc -import dash_html_components as html -import dash_table - - -ID_PREFIX = "app_data_updating_graph_be" -IDS = { - "table": ID_PREFIX, - "container": "{}-container".format(ID_PREFIX), - "table-sorting": "{}-sorting".format(ID_PREFIX), - "table-multi-sorting": "{}-multi-sorting".format(ID_PREFIX), - "table-filtering": "{}-filtering".format(ID_PREFIX), - "table-sorting-filtering": "{}-sorting-filtering".format(ID_PREFIX), - "table-paging-selection": "{}-paging-selection".format(ID_PREFIX), - "table-paging-with-graph": "{}-table-paging-with-graph".format(ID_PREFIX), - "table-paging-with-graph-container": "{}-table-paging-with-graph-container".format( - ID_PREFIX - ), -} -PAGE_SIZE = 5 - - -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", - ) - ) - ) - df = df[df["year"] == 2007] - df["index"] = range(1, len(df) + 1) - - app = dash.Dash( - __name__, external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], - ) - app.config.suppress_callback_exceptions = True - - def section_title(title): - return html.H4(title, style={"marginTop": "20px"}) - - app.layout = html.Div( - [ - section_title("Backend Paging"), - dash_table.DataTable( - id=IDS["table"], - columns=[ - {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) - ], - page_current=0, - page_size=PAGE_SIZE, - page_action="custom", - ), - html.Hr(), - dcc.Markdown( - dedent( - """ - With backend paging, we can have front-end sorting and filtering - but it will only filter and sort the data that exists on the page. - - This should be avoided. Your users will expect - that sorting and filtering is happening on the entire dataset and, - with large pages, might not be aware that this is only occuring - on the current page. - - Instead, we recommend implementing sorting and filtering on the - backend as well. That is, on the entire underlying dataset. - """ - ) - ), - section_title("Backend Paging with Sorting"), - dash_table.DataTable( - id=IDS["table-sorting"], - columns=[ - {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) - ], - page_current=0, - page_size=PAGE_SIZE, - page_action="custom", - sort_action="custom", - sort_mode="single", - sort_by=[], - ), - section_title("Backend Paging with Multi Column Sorting"), - dcc.Markdown( - dedent( - """ - Multi-column sort allows you to sort by multiple columns. - This is useful when you have categorical columns with repeated - values and you're interested in seeing the sorted values for - each category. - - In this example, try sorting by continent and then any other column. - """ - ) - ), - dash_table.DataTable( - id=IDS["table-multi-sorting"], - columns=[ - {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) - ], - page_current=0, - page_size=PAGE_SIZE, - page_action="custom", - sort_action="custom", - sort_mode="multi", - sort_by=[], - ), - section_title("Backend Paging with Filtering"), - dcc.Markdown( - dedent( - """ - Dash Table's front-end filtering has its own filtering expression - language. - - Currently, backend filtering must parse the same filtering language. - If you write an expression that is not "valid" under the filtering - language, then it will not be passed to the backend. - - This limitation will be removed in the future to allow you to - write your own expression query language. - - In this example, we've written a Pandas backend for the filtering - language. It supports `eq`, `<`, and `>`. For example, try: - - - Enter `eq Asia` in the "continent" column - - Enter `> 5000` in the "gdpPercap" column - - Enter `< 80` in the `lifeExp` column - - """ - ) - ), - dash_table.DataTable( - id=IDS["table-filtering"], - columns=[ - {"name": i, "id": i, "deletable": True} for i in sorted(df.columns) - ], - page_current=0, - page_size=PAGE_SIZE, - page_action="custom", - filter_action="custom", - filter_query="", - ), - 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) - ], - page_current=0, - page_size=PAGE_SIZE, - page_action="custom", - filter_action="custom", - filter_query="", - sort_action="custom", - sort_mode="multi", - sort_by=[], - ), - section_title("Connecting Backend Paging with a Graph"), - dcc.Markdown( - dedent( - """ - This final example ties it all together: the graph component - displays the current page of the `data`. - """ - ) - ), - html.Div( - className="row", - children=[ - html.Div( - dash_table.DataTable( - id=IDS["table-paging-with-graph"], - columns=[ - {"name": i, "id": i, "deletable": True} - for i in sorted(df.columns) - ], - page_current=0, - page_size=20, - page_action="custom", - filter_action="custom", - filter_query="", - sort_action="custom", - sort_mode="multi", - sort_by=[], - ), - style={"height": 750, "overflowY": "scroll"}, - className="six columns", - ), - html.Div( - id=IDS["table-paging-with-graph-container"], - className="six columns", - ), - ], - ), - html.Div(id="waitfor"), - ] - ) - - @app.callback( - Output(IDS["table"], "data"), - [Input(IDS["table"], "page_current"), Input(IDS["table"], "page_size")], - ) - def update_data(page_current, page_size): - return df.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-sorting"], "data"), - [ - Input(IDS["table-sorting"], "page_current"), - Input(IDS["table-sorting"], "page_size"), - Input(IDS["table-sorting"], "sort_by"), - ], - ) - def update_graph(page_current, page_size, sort_by): - # print(sort_by) - if len(sort_by): - dff = df.sort_values( - sort_by[0]["columnId"], - ascending=sort_by[0]["direction"] == "asc", - inplace=False, - ) - else: - # No sort is applied - dff = df - - return dff.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-multi-sorting"], "data"), - [ - Input(IDS["table-multi-sorting"], "page_current"), - Input(IDS["table-multi-sorting"], "page_size"), - Input(IDS["table-multi-sorting"], "sort_by"), - ], - ) - def update_multi_data(page_current, page_size, sort_by): - # print(sort_by) - if len(sort_by): - dff = df.sort_values( - [col["columnId"] for col in sort_by], - ascending=[col["direction"] == "asc" for col in sort_by], - inplace=False, - ) - else: - # No sort is applied - dff = df - - return dff.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-filtering"], "data"), - [ - Input(IDS["table-filtering"], "page_current"), - Input(IDS["table-filtering"], "page_size"), - Input(IDS["table-filtering"], "filter_query"), - ], - ) - def updat_filtering_data(page_current, page_size, filter_query): - # print(filter_query) - filtering_expressions = filter_query.split(" && ") - dff = df - for filter_query in filtering_expressions: - if " eq " in filter_query: - col_name = filter_query.split(" eq ")[0] - filter_value = filter_query.split(" eq ")[1] - dff = dff.loc[dff[col_name] == filter_value] - if " > " in filter_query: - col_name = filter_query.split(" > ")[0] - filter_value = float(filter_query.split(" > ")[1]) - dff = dff.loc[dff[col_name] > filter_value] - if " < " in filter_query: - col_name = filter_query.split(" < ")[0] - filter_value = float(filter_query.split(" < ")[1]) - dff = dff.loc[dff[col_name] < filter_value] - - return dff.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-sorting-filtering"], "data"), - [ - Input(IDS["table-sorting-filtering"], "page_current"), - Input(IDS["table-sorting-filtering"], "page_size"), - Input(IDS["table-sorting-filtering"], "sort_by"), - Input(IDS["table-sorting-filtering"], "filter_query"), - ], - ) - def update_sorting_filtering_data(page_current, page_size, sort_by, filter_query): - filtering_expressions = filter_query.split(" && ") - dff = df - for filter_query in filtering_expressions: - if " eq " in filter_query: - col_name = filter_query.split(" eq ")[0] - filter_value = filter_query.split(" eq ")[1] - dff = dff.loc[dff[col_name] == filter_value] - if " > " in filter_query: - col_name = filter_query.split(" > ")[0] - filter_value = float(filter_query.split(" > ")[1]) - dff = dff.loc[dff[col_name] > filter_value] - if " < " in filter_query: - col_name = filter_query.split(" < ")[0] - filter_value = float(filter_query.split(" < ")[1]) - dff = dff.loc[dff[col_name] < filter_value] - - if len(sort_by): - dff = dff.sort_values( - [col["columnId"] for col in sort_by], - ascending=[col["direction"] == "asc" for col in sort_by], - inplace=False, - ) - - return dff.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-paging-with-graph"], "data"), - [ - Input(IDS["table-paging-with-graph"], "page_current"), - Input(IDS["table-paging-with-graph"], "page_size"), - Input(IDS["table-paging-with-graph"], "sort_by"), - Input(IDS["table-paging-with-graph"], "filter_query"), - ], - ) - def update_table(page_current, page_size, sort_by, filter_query): - filtering_expressions = filter_query.split(" && ") - dff = df - for filter_query in filtering_expressions: - if " eq " in filter_query: - col_name = filter_query.split(" eq ")[0] - filter_value = filter_query.split(" eq ")[1] - dff = dff.loc[dff[col_name] == filter_value] - if " > " in filter_query: - col_name = filter_query.split(" > ")[0] - filter_value = float(filter_query.split(" > ")[1]) - dff = dff.loc[dff[col_name] > filter_value] - if " < " in filter_query: - col_name = filter_query.split(" < ")[0] - filter_value = float(filter_query.split(" < ")[1]) - dff = dff.loc[dff[col_name] < filter_value] - - if len(sort_by): - dff = dff.sort_values( - [col["columnId"] for col in sort_by], - ascending=[col["direction"] == "asc" for col in sort_by], - inplace=False, - ) - - return dff.iloc[ - page_current * page_size : (page_current + 1) * page_size - ].to_dict("rows") - - @app.callback( - Output(IDS["table-paging-with-graph-container"], "children"), - [Input(IDS["table-paging-with-graph"], "data")], - ) - def update_children(rows): - dff = pd.DataFrame(rows) - return html.Div( - [ - dcc.Graph( - id=column, - figure={ - "data": [ - { - "x": dff["country"], - "y": dff[column] if column in dff else [], - "type": "bar", - "marker": {"color": "#0074D9"}, - } - ], - "layout": { - "xaxis": {"automargin": True}, - "yaxis": {"automargin": True}, - "height": 250, - "margin": {"t": 10, "l": 10, "r": 10}, - }, - }, - ) - for column in ["pop", "lifeExp", "gdpPercap"] - ] - ) - - dash_duo.start_server(app) - dash_duo.wait_for_element("#waitfor") - dash_duo.percy_snapshot("rapp001 - loaded") diff --git a/tests/integration/review_app/test_app_df_graph.py b/tests/integration/review_app/test_app_df_graph.py deleted file mode 100644 index d2c1a3ecf..000000000 --- a/tests/integration/review_app/test_app_df_graph.py +++ /dev/null @@ -1,159 +0,0 @@ -import os -from textwrap import dedent -import pandas as pd - -import dash -from dash.dependencies import Input, Output -import dash_core_components as dcc -import dash_html_components as html -import dash_table - -ID_PREFIX = "app_data_updating_graph" -IDS = {"table": ID_PREFIX, "container": "{}-container".format(ID_PREFIX)} -_TIMEOUT = 10 - - -def test_rapp002_df_graph(dash_duo): - df = pd.read_csv( - os.path.realpath( - os.path.join( - os.path.dirname(__file__), "..", "..", "assets", "gapminder.csv", - ) - ) - ) - - df = df[df["year"] == 2007] - - app = dash.Dash( - __name__, external_stylesheets=["https://codepen.io/chriddyp/pen/dZVMbK.css"], - ) - app.layout = html.Div( - [ - html.Div( - dash_table.DataTable( - id=IDS["table"], - columns=[ - {"name": i, "id": i, "deletable": True} for i in df.columns - ], - data=df.to_dict("rows"), - editable=True, - filter_action="native", - sort_action="native", - sort_mode="multi", - row_selectable="multi", - row_deletable=True, - selected_rows=[], - fixed_rows={"headers": True}, - ), - style={"height": 300, "overflowY": "scroll"}, - ), - html.Div(id=IDS["container"]), - dcc.Markdown( - dedent( - """ - *** - - `Table` includes several features for modifying and transforming the - view of the data. These include: - - - Sorting by column (`sort_action='native'`) - - Filtering by column (`filter_action='native'`) - - Editing the cells (`editable=True`) - - Deleting rows (`row_deletable=True`) - - Deleting columns (`columns[i].deletable=True`) - - Selecting rows (`row_selectable='single' | 'multi'`) - - > A quick note on filtering. We have defined our own - > syntax for performing filtering operations. Here are some - > examples for this particular dataset: - > - `lt 50` in the `lifeExp` column - > - `eq "Canada"` in the `country` column - - By default, these transformations are done clientside. - Your Dash callbacks can respond to these modifications - by listening to the `data` property as an `Input`. - - Note that if `data` is an `Input` then the entire - `data` will be passed over the network: if your data is - large, then this will become slow. For large data, you have - two options: - - Use `data_indices` instead - - Perform the sorting or filtering in Python instead - - Issues with this example: - - Row selection callbacks don't work yet: `derived_viewport_indices` - isn't getting updated on row selection and `selected_rows` doesn't - track the underlying data (e.g. it will always be [1, 3] even after sorting or filtering) - """ - ) - ), - html.Div(id="waitfor"), - ] - ) - - @app.callback( - Output(IDS["container"], "children"), - [ - Input(IDS["table"], "derived_virtual_data"), - Input(IDS["table"], "selected_rows"), - ], - ) - def update_graph(rows, selected_rows): - # When the table is first rendered, `derived_virtual_data` - # will be `None`. This is due to an idiosyncracy in Dash - # (unsupplied properties are always None and Dash calls the dependent - # callbacks when the component is first rendered). - # So, if `selected_rows` is `None`, then the component was just rendered - # and its value will be the same as the component's data. - # Instead of setting `None` in here, you could also set - # `derived_virtual_data=df.to_rows('dict')` when you initialize - # the component. - if rows is None: - dff = df - else: - dff = pd.DataFrame(rows) - - colors = [] - for i in range(len(dff)): - if i in selected_rows: - colors.append("#7FDBFF") - else: - colors.append("#0074D9") - - return html.Div( - [ - dcc.Graph( - id=column, - figure={ - "data": [ - { - "x": dff["country"], - # check if column exists - user may have deleted it - # If `column.deletable=False`, then you don't - # need to do this check. - "y": dff[column] if column in dff else [], - "type": "bar", - "marker": {"color": colors}, - } - ], - "layout": { - "xaxis": {"automargin": True}, - "yaxis": {"automargin": True}, - "height": 250, - "margin": {"t": 10, "l": 10, "r": 10}, - }, - }, - ) - for column in ["pop", "lifeExp", "gdpPercap"] - ] - ) - - dash_duo.start_server(app) - dash_duo.wait_for_element("#waitfor") - - dash_duo.wait_for_element("#{}".format(IDS["table"])) - dash_duo.wait_for_element("#pop svg") - dash_duo.wait_for_element("#lifeExp svg") - dash_duo.wait_for_element("#gdpPercap svg") - - dash_duo.percy_snapshot("rapp002 - loaded") diff --git a/tests/selenium/assets/logo.png b/tests/selenium/assets/logo.png new file mode 100644 index 000000000..8670bf63e Binary files /dev/null and b/tests/selenium/assets/logo.png differ diff --git a/tests/selenium/conftest.py b/tests/selenium/conftest.py index 050b7e087..176b45381 100644 --- a/tests/selenium/conftest.py +++ b/tests/selenium/conftest.py @@ -354,6 +354,9 @@ def __init__(self, server, **kwargs): self.LOADING = _LOADING self.ANY = _ANY + def get_log_errors(self): + return list(filter(lambda i: i.get("level") != "WARNING", self.get_logs())) + def start_server(self, app, **kwargs): """start the local server with app""" diff --git a/tests/selenium/test_basic_copy_paste.py b/tests/selenium/test_basic_copy_paste.py index 957caa8bf..6d7817e32 100644 --- a/tests/selenium/test_basic_copy_paste.py +++ b/tests/selenium/test_basic_copy_paste.py @@ -85,6 +85,7 @@ def test_tbcp001_copy_paste_callback(test): assert target.cell(1, 0).get_text() == "0" assert target.cell(1, 1).get_text() == "MODIFIED" + assert test.get_log_errors() == [] def test_tbcp002_sorted_copy_paste_callback(test): @@ -112,6 +113,7 @@ def test_tbcp002_sorted_copy_paste_callback(test): assert target.cell(1, 0).get_text() == "11" assert target.cell(2, 1).get_text() == "MODIFIED" + assert test.get_log_errors() == [] @pytest.mark.parametrize("mouse_navigation", [True, False]) @@ -137,6 +139,8 @@ def test_tbcp003_copy_multiple_rows(test, mouse_navigation): assert target.cell(i + 3, 0).get_text() == target.cell(i, 0).get_text() assert target.cell(i + 3, 1).get_text() == "MODIFIED" + assert test.get_log_errors() == [] + def test_tbcp004_copy_9_and_10(test): test.start_server(get_app()) @@ -158,6 +162,8 @@ def test_tbcp004_copy_9_and_10(test): target.cell(row, col).get_text() == source.cell(row + 9, col).get_text() ) + assert test.get_log_errors() == [] + def test_tbcp005_copy_multiple_rows_and_columns(test): test.start_server(get_app()) @@ -178,6 +184,8 @@ def test_tbcp005_copy_multiple_rows_and_columns(test): target.cell(row + 3, col).get_text() == target.cell(row, col).get_text() ) + assert test.get_log_errors() == [] + def test_tbcp006_copy_paste_between_tables(test): test.start_server(get_app()) @@ -200,6 +208,8 @@ def test_tbcp006_copy_paste_between_tables(test): == target.cell(row, col).get_text() ) + assert test.get_log_errors() == [] + def test_tbcp007_copy_paste_with_hidden_column(test): test.start_server(get_app()) @@ -222,6 +232,8 @@ def test_tbcp007_copy_paste_with_hidden_column(test): == target.cell(row + 3, col + 1).get_text() ) + assert test.get_log_errors() == [] + def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): test.start_server(get_app()) @@ -243,3 +255,5 @@ def test_tbcp008_copy_paste_between_tables_with_hidden_columns(test): target.cell(row + 10, col).get_text() == target.cell(row, col).get_text() ) + + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_basic_operations.py b/tests/selenium/test_basic_operations.py index ed51645c1..b9f8aaea6 100644 --- a/tests/selenium/test_basic_operations.py +++ b/tests/selenium/test_basic_operations.py @@ -41,6 +41,7 @@ def test_tbst001_get_cell(test): assert target.cell(0, 0).get_text() == "0" target.paging.next.click() assert target.cell(0, 0).get_text() == "250" + assert test.get_log_errors() == [] def test_tbst002_select_all_text(test): @@ -51,6 +52,7 @@ def test_tbst002_select_all_text(test): target.cell(0, 1).click() assert target.cell(0, 1).get_text() == test.get_selected_text() + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/50 @@ -63,6 +65,7 @@ def test_tbst003_edit_on_enter(test): test.send_keys("abc" + Keys.ENTER) assert target.cell(249, 0).get_text() == "abc" + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/107 @@ -75,6 +78,7 @@ def test_tbst004_edit_on_tab(test): test.send_keys("abc" + Keys.TAB) assert target.cell(249, 0).get_text() == "abc" + assert test.get_log_errors() == [] def test_tbst005_edit_last_row_on_click_outside(test): @@ -87,6 +91,7 @@ def test_tbst005_edit_last_row_on_click_outside(test): target.cell(248, 0).click() assert target.cell(249, 0).get_text() == "abc" + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/141 @@ -100,6 +105,7 @@ def test_tbst006_focused_arrow_left(test): assert target.cell(249, 1).get_text() == "abc" assert target.cell(249, 0).is_focused() + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/141 @@ -113,6 +119,7 @@ def test_tbst007_active_focused_arrow_right(test): assert target.cell(249, 0).get_text() == "abc" assert target.cell(249, 1).is_focused() + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/141 @@ -126,6 +133,7 @@ def test_tbst008_active_focused_arrow_up(test): assert target.cell(249, 0).get_text() == "abc" assert target.cell(248, 0).is_focused() + assert test.get_log_errors() == [] # https://github.com/plotly/dash-table/issues/141 @@ -139,6 +147,7 @@ def test_tbst009_active_focused_arrow_down(test): assert target.cell(249, 0).get_text() == "abc" assert target.cell(249, 0).is_focused() + assert test.get_log_errors() == [] def test_tbst010_active_with_dblclick(test): @@ -149,6 +158,7 @@ def test_tbst010_active_with_dblclick(test): target.cell(0, 0).double_click() assert target.cell(0, 0).is_active() assert target.cell(0, 0).get_text() == test.get_selected_text() + assert test.get_log_errors() == [] def test_tbst011_delete_row(test): @@ -160,6 +170,7 @@ def test_tbst011_delete_row(test): target.row(0).delete() assert target.cell(0, 0).get_text() == text01 + assert test.get_log_errors() == [] def test_tbst012_delete_sorted_row(test): @@ -174,6 +185,7 @@ def test_tbst012_delete_sorted_row(test): target.row(0).delete() assert target.cell(0, 0).get_text() == text01 + assert test.get_log_errors() == [] def test_tbst013_select_row(test): @@ -184,6 +196,7 @@ def test_tbst013_select_row(test): target.row(0).select() assert target.row(0).is_selected() + assert test.get_log_errors() == [] def test_tbst014_selected_sorted_row(test): @@ -196,6 +209,7 @@ def test_tbst014_selected_sorted_row(test): target.row(0).select() assert target.row(0).is_selected() + assert test.get_log_errors() == [] def test_tbst015_selected_row_respects_sort(test): @@ -215,6 +229,7 @@ def test_tbst015_selected_row_respects_sort(test): target.column(rawDf.columns[0]).sort() # DESC -> None assert target.row(0).is_selected() + assert test.get_log_errors() == [] def test_tbst016_delete_cell(test): @@ -227,6 +242,7 @@ def test_tbst016_delete_cell(test): test.send_keys(Keys.ENTER) assert target.cell(0, 1).get_text() == "" + assert test.get_log_errors() == [] @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -239,6 +255,7 @@ def test_tbst017_delete_cell_updates_while_selected(test): test.send_keys(Keys.BACKSPACE) assert target.cell(0, 1).get_text() == "" + assert test.get_log_errors() == [] def test_tbst018_delete_multiple_cells(test): @@ -256,6 +273,8 @@ def test_tbst018_delete_multiple_cells(test): for col in range(1, 3): assert target.cell(row, col).get_text() == "" + assert test.get_log_errors() == [] + @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") def test_tbst019_delete_multiple_cells_while_selected(test): @@ -273,6 +292,8 @@ def test_tbst019_delete_multiple_cells_while_selected(test): for col in range(1, 3): assert target.cell(row, col).get_text() == "" + assert test.get_log_errors() == [] + def test_tbst020_sorted_table_delete_cell(test): test.start_server(get_app()) @@ -287,6 +308,7 @@ def test_tbst020_sorted_table_delete_cell(test): test.send_keys(Keys.ENTER) assert target.cell(0, 1).get_text() == "" + assert test.get_log_errors() == [] @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") @@ -302,6 +324,7 @@ def test_tbst021_sorted_table_delete_cell_updates_while_selected(test): test.send_keys(Keys.BACKSPACE) assert target.cell(0, 1).get_text() == "" + assert test.get_log_errors() == [] def test_tbst022_sorted_table_delete_multiple_cells(test): @@ -322,6 +345,8 @@ def test_tbst022_sorted_table_delete_multiple_cells(test): for col in range(1, 3): assert target.cell(row, col).get_text() == "" + assert test.get_log_errors() == [] + @pytest.mark.skip(reason="https://github.com/plotly/dash-table/issues/700") def test_tbst023_sorted_table_delete_multiple_cells_while_selected(test): @@ -341,3 +366,5 @@ 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(row, col).get_text() == "" + + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_derived_props.py b/tests/selenium/test_derived_props.py index 4b8465449..58d3c95e3 100644 --- a/tests/selenium/test_derived_props.py +++ b/tests/selenium/test_derived_props.py @@ -135,6 +135,7 @@ def test_tdrp001_select_rows(test): assert test.find_element("#derived_virtual_row_ids").get_attribute( "innerHTML" ) == json.dumps(list(range(3000, 3100))) + assert test.get_log_errors() == [] def test_tdrp002_select_cell(test): @@ -192,6 +193,7 @@ def test_tdrp002_select_cell(test): assert test.find_element("#derived_virtual_row_ids").get_attribute( "innerHTML" ) == json.dumps(list(range(3000, 3100))) + assert test.get_log_errors() == [] def test_tdrp003_select_cells(test): @@ -326,6 +328,7 @@ def test_tdrp003_select_cells(test): assert test.find_element("#derived_virtual_row_ids").get_attribute( "innerHTML" ) == json.dumps(list(range(3000, 3100))) + assert test.get_log_errors() == [] def test_tdrp004_navigate_selected_cells(test): @@ -403,6 +406,8 @@ def test_tdrp004_navigate_selected_cells(test): test.send_keys(Keys.TAB) + assert test.get_log_errors() == [] + def test_tdrp005_filtered_and_sorted_row_select(test): test.start_server(get_app()) @@ -565,3 +570,4 @@ def test_tdrp005_filtered_and_sorted_row_select(test): assert test.find_element("#derived_virtual_row_ids").get_attribute( "innerHTML" ) == json.dumps(list(range(3000, 3100, 2))[::-1]) + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_dropdown.py b/tests/selenium/test_dropdown.py index 313313204..c5afc409c 100644 --- a/tests/selenium/test_dropdown.py +++ b/tests/selenium/test_dropdown.py @@ -49,3 +49,4 @@ def test_drpd001_no_scroll(test): cell.open_dropdown() assert get_page_offset(test) == yOffset + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_editable.py b/tests/selenium/test_editable.py index b25b40a76..cd6b6a806 100644 --- a/tests/selenium/test_editable.py +++ b/tests/selenium/test_editable.py @@ -79,6 +79,7 @@ def test_tedi001_loading_on_data_change(test): target.is_ready() assert target.cell(0, 0).get().find_element_by_css_selector("input") is not None + assert test.get_log_errors() == [] def test_tedi002_ready_on_non_data_change(test): @@ -96,6 +97,7 @@ def test_tedi002_ready_on_non_data_change(test): target.is_ready() assert target.cell(0, 0).get().find_element_by_css_selector("input") is not None + assert test.get_log_errors() == [] def test_tedi003_does_not_steal_focus(test): @@ -112,6 +114,7 @@ def test_tedi003_does_not_steal_focus(test): target.is_ready() assert test.find_element("#input") == test.driver.switch_to.active_element + assert test.get_log_errors() == [] def test_tedi004_edit_on_non_blocking(test): @@ -127,6 +130,8 @@ def test_tedi004_edit_on_non_blocking(test): test.send_keys("abc" + Keys.ENTER) assert target.cell(0, 0).get_text() == "abc" + assert test.get_log_errors() == [] + def test_tedi005_prevent_copy_paste_on_blocking(test): app, blocking, non_blocking = get_app_and_locks() @@ -152,6 +157,8 @@ def test_tedi005_prevent_copy_paste_on_blocking(test): != target.cell(row, col).get_text() ) + assert test.get_log_errors() == [] + def test_tedi006_allow_copy_paste_on_non_blocking(test): app, blocking, non_blocking = get_app_and_locks() @@ -176,3 +183,5 @@ def test_tedi006_allow_copy_paste_on_non_blocking(test): target.cell(row + 2, col).get_text() == target.cell(row, col).get_text() ) + + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_empty.py b/tests/selenium/test_empty.py index 6c62b9e22..cb81cb2ab 100644 --- a/tests/selenium/test_empty.py +++ b/tests/selenium/test_empty.py @@ -44,3 +44,4 @@ def test_empt001_clear_(test): test.driver.find_element_by_css_selector("#clear-table").click() assert target.is_ready() assert len(test.driver.find_elements_by_css_selector("tr")) == 0 + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_filter.py b/tests/selenium/test_filter.py index dcf3ab842..90c8ffd9b 100644 --- a/tests/selenium/test_filter.py +++ b/tests/selenium/test_filter.py @@ -82,3 +82,5 @@ def test_filt001_basic(test, props, expect): for index, value in enumerate(expect): assert target.cell(index, "a").get_text() == value + + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_markdown_copy_paste.py b/tests/selenium/test_markdown_copy_paste.py index 602e4e2d2..e763f9b91 100644 --- a/tests/selenium/test_markdown_copy_paste.py +++ b/tests/selenium/test_markdown_copy_paste.py @@ -1,4 +1,5 @@ import dash +from dash.testing import wait from dash_table import DataTable import pandas as pd @@ -7,9 +8,7 @@ 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["Issue"] = rawDf["Issue"].map(lambda x: "![" + str(x) + "](assets/logo.png)") rawDf["State"] = rawDf["State"].map(lambda x: '```python\n"{}"\n```'.format(x)) df = rawDf.to_dict("rows") @@ -49,7 +48,8 @@ def test_tmcp001_copy_markdown_to_text(test): target.cell(0, "Sub-product").click() test.paste() - assert target.cell(0, 2).get_text() == df[0].get("Issue") + wait.until(lambda: target.cell(0, 2).get_text() == df[0].get("Issue"), 3) + assert test.get_log_errors() == [] def test_tmcp002_copy_markdown_to_markdown(test): @@ -63,10 +63,12 @@ def test_tmcp002_copy_markdown_to_markdown(test): target.cell(0, "Complaint ID").click() test.paste() - assert ( - target.cell(0, "Complaint ID").get_text() - == target.cell(0, "Product").get_text() + wait.until( + lambda: target.cell(0, "Complaint ID").get_text() + == target.cell(0, "Product").get_text(), + 3, ) + assert test.get_log_errors() == [] def test_tmcp003_copy_text_to_markdown(test): @@ -80,9 +82,15 @@ def test_tmcp003_copy_text_to_markdown(test): target.cell(1, "Product").click() test.paste() - assert target.cell(1, "Product").get().find_element_by_css_selector( - ".dash-cell-value > p" - ).get_attribute("innerHTML") == df[1].get("Sub-product") + wait.until( + lambda: target.cell(1, "Product") + .get() + .find_element_by_css_selector(".dash-cell-value > p") + .get_attribute("innerHTML") + == df[1].get("Sub-product"), + 3, + ) + assert test.get_log_errors() == [] def test_tmcp004_copy_null_text_to_markdown(test): @@ -96,10 +104,12 @@ def test_tmcp004_copy_null_text_to_markdown(test): target.cell(0, "Product").click() test.paste() - assert ( - target.cell(0, "Product") + wait.until( + lambda: target.cell(0, "Product") .get() .find_element_by_css_selector(".dash-cell-value > p") .get_attribute("innerHTML") - == "null" + == "null", + 3, ) + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_markdown_link.py b/tests/selenium/test_markdown_link.py index 9ccd08ffa..fd7f4f048 100644 --- a/tests/selenium/test_markdown_link.py +++ b/tests/selenium/test_markdown_link.py @@ -63,3 +63,5 @@ def test_tmdl001_click_markdown_link(test, markdown_options, new_tab, cell_selec else: assert len(test.driver.window_handles) == 1 assert test.driver.current_url.startswith("https://www.google.com") + + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_navigation.py b/tests/selenium/test_navigation.py index 9a8f2f2b5..1740bd830 100644 --- a/tests/selenium/test_navigation.py +++ b/tests/selenium/test_navigation.py @@ -135,6 +135,7 @@ def test_navg001_keyboard_through_9_10_cells(test, props): assert target.cell(10, 2).is_focused() test.send_keys(Keys.ENTER) assert target.cell(9, 1).is_focused() + assert test.get_log_errors() == [] @pytest.mark.parametrize("props", [get_markdown_table(), get_mixed_markdown_table()]) @@ -149,6 +150,7 @@ def test_navg002_keyboard_after_ctrl_copy(test, props): assert target.cell(4, 1).is_focused() assert not target.cell(3, 1).is_focused() + assert test.get_log_errors() == [] @pytest.mark.parametrize("props", [get_markdown_table(), get_mixed_markdown_table()]) @@ -170,6 +172,7 @@ def test_navg003_keyboard_can_move_down(test, props, key, row, col): assert target.cell(3 + row, 1 + col).is_focused() assert not target.cell(3, 1).is_focused() + assert test.get_log_errors() == [] @pytest.mark.parametrize("props", [get_mixed_markdown_table()]) @@ -186,6 +189,8 @@ def test_navg004_keyboard_between_md_and_standard_cells(test, props): test.send_keys(Keys.ARROW_DOWN) assert target.cell(i, i).is_focused() + assert test.get_log_errors() == [] + @pytest.mark.parametrize("cell_selectable", [True, False]) def test_navg005_unselectable_cells(test, cell_selectable): @@ -203,3 +208,4 @@ def test_navg005_unselectable_cells(test, cell_selectable): target.cell(0, "a").click() assert target.cell(0, "a").is_selected() == cell_selectable + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_pagination.py b/tests/selenium/test_pagination.py index 324206ad1..94154abfd 100644 --- a/tests/selenium/test_pagination.py +++ b/tests/selenium/test_pagination.py @@ -74,6 +74,7 @@ def test_tpag001_next_previous(test, mode): assert target.cell(0, 0).get_text() == "0" assert target.paging.next.exists() assert not target.paging.previous.exists() + assert test.get_log_errors() == [] @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -87,6 +88,7 @@ def test_tpag002_ops_on_first_page(test, mode): assert not target.paging.previous.exists() assert target.paging.next.exists() assert target.paging.last.exists() + assert test.get_log_errors() == [] @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -102,6 +104,7 @@ def test_tpag003_ops_on_last_page(test, mode): assert target.paging.previous.exists() assert not target.paging.next.exists() assert not target.paging.last.exists() + assert test.get_log_errors() == [] def test_tpag004_ops_input_with_enter(test): @@ -118,6 +121,7 @@ def test_tpag004_ops_input_with_enter(test): assert target.paging.current.get_value() == "100" assert target.cell(0, 0).get_text() != text00 + assert test.get_log_errors() == [] def test_tpag005_ops_input_with_unfocus(test): @@ -135,6 +139,7 @@ def test_tpag005_ops_input_with_unfocus(test): assert target.paging.current.get_value() == "100" assert target.cell(0, 0).get_text() != text00 + assert test.get_log_errors() == [] @pytest.mark.parametrize( @@ -151,6 +156,7 @@ def test_tpag006_ops_input_invalid_with_enter(test, value, expected_value): test.send_keys(str(value) + Keys.ENTER) assert target.paging.current.get_value() == str(expected_value) + assert test.get_log_errors() == [] @pytest.mark.parametrize( @@ -168,6 +174,7 @@ def test_tpag007_ops_input_invalid_with_unfocus(test, value, expected_value): target.cell(0, 0).click() assert target.paging.current.get_value() == str(expected_value) + assert test.get_log_errors() == [] @pytest.mark.parametrize("mode", ["custom", "native"]) @@ -177,6 +184,7 @@ def test_tpag008_hide_with_single_page(test, mode): target = test.table("table") assert not target.paging.exists() + assert test.get_log_errors() == [] def test_tpag009_hide_with_invalid_page_count(test): @@ -185,6 +193,7 @@ def test_tpag009_hide_with_invalid_page_count(test): target = test.table("table") assert not target.paging.exists() + assert test.get_log_errors() == [] def test_tpag010_limits_page(test): @@ -195,3 +204,4 @@ def test_tpag010_limits_page(test): target.paging.last.click() assert target.paging.current.get_value() == "10" + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_scrolling.py b/tests/selenium/test_scrolling.py index 4ab8e7d53..bd7cea152 100644 --- a/tests/selenium/test_scrolling.py +++ b/tests/selenium/test_scrolling.py @@ -82,6 +82,7 @@ def test_scrol001_fixed_alignment(test, fixed_rows, fixed_columns, ops): wait.until( lambda: -get_margin(test) == fixed_width, 3, ) + assert test.get_log_errors() == [] @pytest.mark.parametrize( @@ -131,3 +132,4 @@ def test_scrol002_edit_navigate(test, fixed_rows, fixed_columns, ops): wait.until( lambda: -get_margin(test) == fixed_width + get_scroll(test), 3, ) + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_sizing.py b/tests/selenium/test_sizing.py index d64907eda..c3716e0fa 100644 --- a/tests/selenium/test_sizing.py +++ b/tests/selenium/test_sizing.py @@ -2,6 +2,7 @@ import pytest from dash.dependencies import Input, Output +from dash.exceptions import PreventUpdate from dash.testing import wait from dash_table import DataTable from dash_html_components import Button, Div @@ -182,6 +183,8 @@ def update_styles(n_clicks): if n_clicks < len(styles): style_table = styles[n_clicks]["style_table"] return [style_table for i in variations_range] + else: + raise PreventUpdate test.start_server(app) @@ -213,6 +216,8 @@ def update_styles(n_clicks): test.driver.find_element_by_css_selector("#btn").click() + assert test.get_log_errors() == [] + def test_szng002_percentages_result_in_same_widths(test): _fixed_columns = [dict(headers=True, data=1), dict(headers=True)] @@ -249,6 +254,8 @@ def test_szng002_percentages_result_in_same_widths(test): table = test.driver.find_element_by_css_selector("#table{}".format(i)) cells_are_same_width(target, table) + assert test.get_log_errors() == [] + @pytest.mark.parametrize( "fixed_columns", @@ -321,3 +328,5 @@ def callback(n_clicks): test.driver.find_element_by_css_selector("#btn").click() cells_are_same_width(target, target) + + assert test.get_log_errors() == [] diff --git a/tests/integration/test_table_export_csv.py b/tests/selenium/test_table_export_csv.py similarity index 70% rename from tests/integration/test_table_export_csv.py rename to tests/selenium/test_table_export_csv.py index 59e6bdaf6..ee86b1704 100644 --- a/tests/integration/test_table_export_csv.py +++ b/tests/selenium/test_table_export_csv.py @@ -5,7 +5,7 @@ import dash.testing.wait as wait -def test_tbex001_table_export(dash_duo): +def test_tbex001_table_export(test): df = pd.read_csv( "https://raw.githubusercontent.com/plotly/datasets/master/solar.csv" ) @@ -16,11 +16,12 @@ def test_tbex001_table_export(dash_duo): data=df.to_dict("records"), export_format="csv", ) - dash_duo.start_server(app) - dash_duo.wait_for_element(".export", timeout=1).click() + test.start_server(app) + test.wait_for_element(".export", timeout=1).click() - download = os.path.sep.join((dash_duo.download_path, "Data.csv")) + download = os.path.sep.join((test.download_path, "Data.csv")) wait.until(lambda: os.path.exists(download), timeout=2) df_bis = pd.read_csv(download) assert df_bis.equals(df) + assert test.get_log_errors() == [] diff --git a/tests/integration/test_table_export_xlsx.py b/tests/selenium/test_table_export_xlsx.py similarity index 71% rename from tests/integration/test_table_export_xlsx.py rename to tests/selenium/test_table_export_xlsx.py index 83a757bd3..7c4f47864 100644 --- a/tests/integration/test_table_export_xlsx.py +++ b/tests/selenium/test_table_export_xlsx.py @@ -5,7 +5,7 @@ import dash.testing.wait as wait -def test_tbex001_table_export(dash_duo): +def test_tbex001_table_export(test): df = pd.read_csv( "https://raw.githubusercontent.com/plotly/datasets/master/solar.csv" ) @@ -16,11 +16,12 @@ def test_tbex001_table_export(dash_duo): data=df.to_dict("records"), export_format="xlsx", ) - dash_duo.start_server(app) - dash_duo.wait_for_element(".export").click() + test.start_server(app) + test.wait_for_element(".export").click() - download = os.path.sep.join((dash_duo.download_path, "Data.xlsx")) + download = os.path.sep.join((test.download_path, "Data.xlsx")) wait.until(lambda: os.path.exists(download), timeout=2) df_bis = pd.read_excel(download) assert df_bis.equals(df) + assert test.get_log_errors() == [] diff --git a/tests/selenium/test_tooltip.py b/tests/selenium/test_tooltip.py index 3017d8a3b..24527f04a 100644 --- a/tests/selenium/test_tooltip.py +++ b/tests/selenium/test_tooltip.py @@ -87,6 +87,7 @@ def test_ttip001_displays_aligned_tooltip(test, fixed_rows, fixed_columns, ops): cell = target.cell(0, len(columns) - 1) cell.move_to() assert_aligned(cell.get(), tooltip.get()) + assert test.get_log_errors() == [] @pytest.mark.parametrize( @@ -114,6 +115,7 @@ def test_ttip002_displays_tooltip_content(test, tooltip_data, expected_text): cell.move_to() assert tooltip.exists() assert tooltip.get_text().strip() == expected_text + assert test.get_log_errors() == [] def test_ttip003_tooltip_disappears(test): @@ -139,6 +141,7 @@ def test_ttip003_tooltip_disappears(test): wait.until(lambda: tooltip.exists(), 2.5) wait.until(lambda: not tooltip.exists(), 2.5) + assert test.get_log_errors() == [] ttip004_tooltip = { @@ -245,3 +248,5 @@ def test_ttip004_tooltip_applied( wait.until(lambda: target.tooltip.exists(), 3) assert tooltip.get_text().strip() == "text3" cell55.move_to() + + assert test.get_log_errors() == [] diff --git a/tests/visual/percy-storybook/MarkdownCells.percy.tsx b/tests/visual/percy-storybook/MarkdownCells.percy.tsx index 2fc336a53..ecdd88f40 100644 --- a/tests/visual/percy-storybook/MarkdownCells.percy.tsx +++ b/tests/visual/percy-storybook/MarkdownCells.percy.tsx @@ -109,6 +109,129 @@ variants.forEach(variant => { columns={columnFormats} {...props} />)) + .add(`Syntax highlighting${name}`, () => ( + ', + ' ', + '
Hello World
', + ' ', + '', + '```'].join('\n'), + b: 'http' + }, + { + a: ['```javascript', + 'function getDate() {', + ' return new Date();', + '}', + '```'].join('\n'), + b: 'javascript' + }, + { + a: ['```json', + '{', + ' "prop": "value"', + '}', + '```'].join('\n'), + b: 'json' + }, + { + a: ['```julia', + 'function init(r)', + ' println("hello world")', + 'end', + '```'].join('\n'), + b: 'julia' + }, + { + a: ['```markdown', + '# Hello', + '## World', + '```'].join('\n'), + b: 'markdown' + }, + { + a: ['```python', + 'def hello_world():', + ' print("hello, world!")', + '```'].join('\n'), + b: 'code block with syntax highlighting' + }, + { + a: ['```r', + 'print("Hello World!")', + '```'].join('\n'), + b: 'r' + }, + { + a: ['```ruby', + 'puts "Hello World"', + '```'].join('\n'), + b: 'ruby' + }, + { + a: ['```shell', + '#!/bin/sh', + 'echo "Hello world"', + '```'].join('\n'), + b: 'shell' + }, + { + a: ['```sql', + 'SELECT first_name, last_name FROM person', + '```'].join('\n'), + b: 'sql' + }, + { + a: ['```xml', + '', + ' John', + ' Doe', + '', + '```'].join('\n'), + b: 'xml' + }, + { + a: ['```yaml', + 'obj:', + ' prop: value', + '', + 'list:', + ' - value1', + ' - value2', + '```'].join('\n'), + b: 'yaml' + }, + ]} + columns={columnFormats} + {...props} + />)) .add(`Quotes, code, and syntax highlighting${name}`, () => (