diff --git a/CHANGELOG.md b/CHANGELOG.md index bf01c2418..53f1b2fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] +### Added +- [#850](https://github.com/plotly/dash-core-components/pull/850) Add property `prependData` to `Graph` to support `Plotly.prependTraces` + + refactored the existing `extendTraces` API to be a single `mergeTraces` API that can handle both `prepend` as well as `extend`. + ## [1.12.0] - 2020-09-03 ### Updated - [#858](https://github.com/plotly/dash-core-components/pull/858) diff --git a/src/components/Graph.react.js b/src/components/Graph.react.js index 204131bf2..2b4aaf17f 100644 --- a/src/components/Graph.react.js +++ b/src/components/Graph.react.js @@ -9,7 +9,7 @@ import { privateDefaultProps, } from '../fragments/Graph.privateprops'; -const EMPTY_EXTEND_DATA = []; +const EMPTY_DATA = []; /** * Graph can be used to render any plotly.js-powered data visualization. @@ -22,13 +22,19 @@ class PlotlyGraph extends Component { super(props); this.state = { + prependData: [], extendData: [], }; - this.clearExtendData = this.clearExtendData.bind(this); + this.clearState = this.clearState.bind(this); } componentDidMount() { + if (this.props.prependData) { + this.setState({ + prependData: [this.props.prependData], + }); + } if (this.props.extendData) { this.setState({ extendData: [this.props.extendData], @@ -38,15 +44,37 @@ class PlotlyGraph extends Component { componentWillUnmount() { this.setState({ + prependData: [], extendData: [], }); } UNSAFE_componentWillReceiveProps(nextProps) { + let prependData = this.state.prependData.slice(0); + + if (this.props.figure !== nextProps.figure) { + prependData = EMPTY_DATA; + } + + if ( + nextProps.prependData && + this.props.prependData !== nextProps.prependData + ) { + prependData.push(nextProps.prependData); + } else { + prependData = EMPTY_DATA; + } + + if (prependData !== EMPTY_DATA) { + this.setState({ + prependData, + }); + } + let extendData = this.state.extendData.slice(0); if (this.props.figure !== nextProps.figure) { - extendData = EMPTY_EXTEND_DATA; + extendData = EMPTY_DATA; } if ( @@ -55,22 +83,23 @@ class PlotlyGraph extends Component { ) { extendData.push(nextProps.extendData); } else { - extendData = EMPTY_EXTEND_DATA; + extendData = EMPTY_DATA; } - if (extendData !== EMPTY_EXTEND_DATA) { + if (extendData !== EMPTY_DATA) { this.setState({ extendData, }); } } - clearExtendData() { - this.setState(({extendData}) => { + clearState(dataKey) { + this.setState(props => { + var data = props[dataKey]; const res = - extendData && extendData.length + data && data.length ? { - extendData: EMPTY_EXTEND_DATA, + [dataKey]: EMPTY_DATA, } : undefined; @@ -82,8 +111,9 @@ class PlotlyGraph extends Component { return ( ); } @@ -191,6 +221,18 @@ PlotlyGraph.propTypes = { */ extendData: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), + /** + * Data that should be prepended to existing traces. Has the form + * `[updateData, traceIndices, maxPoints]`, where `updateData` is an object + * containing the data to prepend, `traceIndices` (optional) is an array of + * trace indices that should be prepended, and `maxPoints` (optional) is + * either an integer defining the maximum number of points allowed or an + * object with key:value pairs matching `updateData` + * Reference the Plotly.prependTraces API for full usage: + * https://plotly.com/javascript/plotlyjs-function-reference/#plotlyprependtraces + */ + prependData: PropTypes.oneOfType([PropTypes.array, PropTypes.object]), + /** * Data from latest restyle event which occurs * when the user toggles a legend item, changes @@ -523,6 +565,7 @@ PlotlyGraph.defaultProps = { hoverData: null, selectedData: null, relayoutData: null, + prependData: null, extendData: null, restyleData: null, figure: { diff --git a/src/fragments/Graph.react.js b/src/fragments/Graph.react.js index 7ef855787..67fd4fd09 100644 --- a/src/fragments/Graph.react.js +++ b/src/fragments/Graph.react.js @@ -188,18 +188,16 @@ class PlotlyGraph extends Component { }); } - extend(props) { - const {clearExtendData, extendData: extendDataArray} = props; + mergeTraces(props, dataKey, plotlyFnKey) { + const clearState = props.clearState; + const dataArray = props[dataKey]; - extendDataArray.forEach(extendData => { + dataArray.forEach(data => { let updateData, traceIndices, maxPoints; - if ( - Array.isArray(extendData) && - typeof extendData[0] === 'object' - ) { - [updateData, traceIndices, maxPoints] = extendData; + if (Array.isArray(data) && typeof data[0] === 'object') { + [updateData, traceIndices, maxPoints] = data; } else { - updateData = extendData; + updateData = data; } if (!traceIndices) { @@ -214,9 +212,9 @@ class PlotlyGraph extends Component { } const gd = this.gd.current; - return Plotly.extendTraces(gd, updateData, traceIndices, maxPoints); + return Plotly[plotlyFnKey](gd, updateData, traceIndices, maxPoints); }); - clearExtendData(); + clearState(dataKey); } getConfig(config, responsive) { @@ -348,8 +346,11 @@ class PlotlyGraph extends Component { componentDidMount() { this.plot(this.props); + if (this.props.prependData) { + this.mergeTraces(this.props, 'prependData', 'prependTraces'); + } if (this.props.extendData) { - this.extend(this.props); + this.mergeTraces(this.props, 'extendData', 'extendTraces'); } } @@ -392,8 +393,12 @@ class PlotlyGraph extends Component { this.plot(nextProps); } + if (this.props.prependData !== nextProps.prependData) { + this.mergeTraces(nextProps, 'prependData', 'prependTraces'); + } + if (this.props.extendData !== nextProps.extendData) { - this.extend(nextProps); + this.mergeTraces(nextProps, 'extendData', 'extendTraces'); } } @@ -432,14 +437,18 @@ class PlotlyGraph extends Component { PlotlyGraph.propTypes = { ...graphPropTypes, + prependData: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.array, PropTypes.object]) + ), extendData: PropTypes.arrayOf( PropTypes.oneOfType([PropTypes.array, PropTypes.object]) ), - clearExtendData: PropTypes.func.isRequired, + clearState: PropTypes.func.isRequired, }; PlotlyGraph.defaultProps = { ...graphDefaultProps, + prependData: [], extendData: [], }; diff --git a/tests/integration/graph/test_graph_varia.py b/tests/integration/graph/test_graph_varia.py index 868d84d6a..ea26c6178 100644 --- a/tests/integration/graph/test_graph_varia.py +++ b/tests/integration/graph/test_graph_varia.py @@ -192,6 +192,171 @@ def render_content(click, prev_graph): ) +@pytest.mark.parametrize("is_eager", [True, False]) +def test_graph_prepend_trace(dash_dcc, is_eager): + app = dash.Dash(__name__, eager_loading=is_eager) + + def generate_with_id(id, data=None): + if data is None: + data = [{"x": [10, 11, 12, 13, 14], "y": [0, 0.5, 1, 0.5, 0]}] + + return html.Div( + [ + html.P(id), + dcc.Graph(id=id, figure=dict(data=data)), + html.Div(id="output_{}".format(id)), + ] + ) + + figs = [ + "trace_will_prepend", + "trace_will_prepend_with_no_indices", + "trace_will_prepend_with_max_points", + ] + + layout = [generate_with_id(id) for id in figs] + + figs.append("trace_will_allow_repeated_prepend") + data = [{"y": [0, 0, 0]}] + layout.append(generate_with_id(figs[-1], data)) + + figs.append("trace_will_prepend_selectively") + data = [ + {"x": [10, 11, 12, 13, 14], "y": [0, 0.5, 1, 0.5, 0]}, + {"x": [10, 11, 12, 13, 14], "y": [1, 1, 1, 1, 1]}, + ] + layout.append(generate_with_id(figs[-1], data)) + + layout.append( + dcc.Interval( + id="interval_prependablegraph_update", + interval=10, + n_intervals=0, + max_intervals=1, + ) + ) + + layout.append( + dcc.Interval( + id="interval_prependablegraph_prependtwice", + interval=500, + n_intervals=0, + max_intervals=2, + ) + ) + + app.layout = html.Div(layout) + + @app.callback( + Output("trace_will_allow_repeated_prepend", "prependData"), + [Input("interval_prependablegraph_prependtwice", "n_intervals")], + ) + def trace_will_allow_repeated_prepend(n_intervals): + if n_intervals is None or n_intervals < 1: + raise PreventUpdate + + return dict(y=[[0.1, 0.2, 0.3, 0.4, 0.5]]) + + @app.callback( + Output("trace_will_prepend", "prependData"), + [Input("interval_prependablegraph_update", "n_intervals")], + ) + def trace_will_prepend(n_intervals): + if n_intervals is None or n_intervals < 1: + raise PreventUpdate + + x_new = [5, 6, 7, 8, 9] + y_new = [0.1, 0.2, 0.3, 0.4, 0.5] + return dict(x=[x_new], y=[y_new]), [0] + + @app.callback( + Output("trace_will_prepend_selectively", "prependData"), + [Input("interval_prependablegraph_update", "n_intervals")], + ) + def trace_will_prepend_selectively(n_intervals): + if n_intervals is None or n_intervals < 1: + raise PreventUpdate + + x_new = [5, 6, 7, 8, 9] + y_new = [0.1, 0.2, 0.3, 0.4, 0.5] + return dict(x=[x_new], y=[y_new]), [1] + + @app.callback( + Output("trace_will_prepend_with_no_indices", "prependData"), + [Input("interval_prependablegraph_update", "n_intervals")], + ) + def trace_will_prepend_with_no_indices(n_intervals): + if n_intervals is None or n_intervals < 1: + raise PreventUpdate + + x_new = [5, 6, 7, 8, 9] + y_new = [0.1, 0.2, 0.3, 0.4, 0.5] + return dict(x=[x_new], y=[y_new]) + + @app.callback( + Output("trace_will_prepend_with_max_points", "prependData"), + [Input("interval_prependablegraph_update", "n_intervals")], + ) + def trace_will_prepend_with_max_points(n_intervals): + if n_intervals is None or n_intervals < 1: + raise PreventUpdate + + x_new = [5, 6, 7, 8, 9] + y_new = [0.1, 0.2, 0.3, 0.4, 0.5] + return dict(x=[x_new], y=[y_new]), [0], 7 + + for id in figs: + + @app.callback( + Output("output_{}".format(id), "children"), + [Input(id, "prependData")], + [State(id, "figure")], + ) + def display_data(trigger, fig): + return json.dumps(fig["data"]) + + dash_dcc.start_server(app) + + comparison = json.dumps( + [ + dict( + x=[5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + y=[0.1, 0.2, 0.3, 0.4, 0.5, 0, 0.5, 1, 0.5, 0], + ) + ] + ) + dash_dcc.wait_for_text_to_equal("#output_trace_will_prepend", comparison) + dash_dcc.wait_for_text_to_equal( + "#output_trace_will_prepend_with_no_indices", comparison + ) + comparison = json.dumps( + [ + dict(x=[10, 11, 12, 13, 14], y=[0, 0.5, 1, 0.5, 0]), + dict( + x=[5, 6, 7, 8, 9, 10, 11, 12, 13, 14], + y=[0.1, 0.2, 0.3, 0.4, 0.5, 1, 1, 1, 1, 1], + ), + ] + ) + dash_dcc.wait_for_text_to_equal( + "#output_trace_will_prepend_selectively", comparison + ) + + comparison = json.dumps( + [dict(x=[5, 6, 7, 8, 9, 10, 11], y=[0.1, 0.2, 0.3, 0.4, 0.5, 0, 0.5],)] + ) + dash_dcc.wait_for_text_to_equal( + "#output_trace_will_prepend_with_max_points", comparison + ) + + comparison = json.dumps( + [dict(y=[0.1, 0.2, 0.3, 0.4, 0.5, 0.1, 0.2, 0.3, 0.4, 0.5, 0, 0, 0])] + ) + dash_dcc.wait_for_text_to_equal( + "#output_trace_will_allow_repeated_prepend", comparison + ) + + @pytest.mark.parametrize("is_eager", [True, False]) def test_graph_extend_trace(dash_dcc, is_eager): app = dash.Dash(__name__, eager_loading=is_eager) diff --git a/tests/integration/input/test_input_types.py b/tests/integration/input/test_input_basics.py similarity index 67% rename from tests/integration/input/test_input_types.py rename to tests/integration/input/test_input_basics.py index 10607c5ab..c95520250 100644 --- a/tests/integration/input/test_input_types.py +++ b/tests/integration/input/test_input_basics.py @@ -5,7 +5,7 @@ import dash_core_components as dcc import dash_html_components as html -ALLOWING_TYPES = ( +ALLOWED_TYPES = ( "text", "number", "password", @@ -18,7 +18,7 @@ ) -def test_intp001_all_types(dash_dcc): +def test_inbs001_all_types(dash_dcc): def input_id(type_): return "input_{}".format(type_) @@ -26,14 +26,14 @@ def input_id(type_): app.layout = html.Div( [ dcc.Input(id=input_id(_), type=_, placeholder="input type {}".format(_)) - for _ in ALLOWING_TYPES + for _ in ALLOWED_TYPES ] + [html.Div(id="output")] ) @app.callback( Output("output", "children"), - [Input(input_id(_), "value") for _ in ALLOWING_TYPES], + [Input(input_id(_), "value") for _ in ALLOWED_TYPES], ) def cb_render(*vals): return " | ".join((val for val in vals if val)) @@ -46,7 +46,7 @@ def cb_render(*vals): dash_dcc.percy_snapshot("intp001 - init state") - for atype in ALLOWING_TYPES[:-1]: + for atype in ALLOWED_TYPES[:-1]: dash_dcc.find_element("#input_{}".format(atype)).send_keys( "test intp001 - input[{}]".format(atype) ) @@ -54,4 +54,15 @@ def cb_render(*vals): with pytest.raises(WebDriverException): dash_dcc.find_element("#input_hidden").send_keys("no interaction") - dash_dcc.percy_snapshot("intp001 - callback output rendering") + dash_dcc.percy_snapshot("inbs001 - callback output rendering") + + +def test_inbs002_user_class(dash_dcc): + app = dash.Dash(__name__, assets_folder="../../assets") + + app.layout = html.Div(className="test-input-css", children=[dcc.Input()]) + + dash_dcc.start_server(app) + + dash_dcc.find_element(".test-input-css") + dash_dcc.percy_snapshot("styled input - width: 100%, border-color: hotpink") diff --git a/tests/integration/location/test_location_callback.py b/tests/integration/location/test_location_callback.py index 24f589657..2060e23e1 100644 --- a/tests/integration/location/test_location_callback.py +++ b/tests/integration/location/test_location_callback.py @@ -1,9 +1,11 @@ import pytest import dash -from dash.dependencies import Input, Output +from dash.dependencies import Input, Output, State import dash_core_components as dcc import dash_html_components as html +from dash.testing.wait import until + @pytest.mark.DCC774 def test_loca001_callbacks(dash_dcc): @@ -23,3 +25,116 @@ def update_path(path): dash_dcc.start_server(app) dash_dcc.wait_for_text_to_equal("#div", "/") + + +def test_loca002_location_link(dash_dcc): + app = dash.Dash(__name__) + + app.layout = html.Div( + [ + html.Div(id="waitfor"), + dcc.Location(id="test-location", refresh=False), + dcc.Link( + html.Button("I am a clickable button"), + id="test-link", + href="/test/pathname", + ), + dcc.Link( + html.Button("I am a clickable hash button"), + id="test-link-hash", + href="#test", + ), + dcc.Link( + html.Button("I am a clickable search button"), + id="test-link-search", + href="?testQuery=testValue", + refresh=False, + ), + html.Button("I am a magic button that updates pathname", id="test-button"), + html.A("link to click", href="/test/pathname/a", id="test-a"), + html.A("link to click", href="#test-hash", id="test-a-hash"), + html.A("link to click", href="?queryA=valueA", id="test-a-query"), + html.Div(id="test-pathname", children=[]), + html.Div(id="test-hash", children=[]), + html.Div(id="test-search", children=[]), + ] + ) + + @app.callback( + Output("test-pathname", "children"), Input("test-location", "pathname") + ) + def update_test_pathname(pathname): + return pathname + + @app.callback(Output("test-hash", "children"), Input("test-location", "hash")) + def update_test_hash(hash_val): + return hash_val or "" + + @app.callback(Output("test-search", "children"), Input("test-location", "search")) + def update_test_search(search): + return search or "" + + @app.callback( + Output("test-location", "pathname"), + Input("test-button", "n_clicks"), + State("test-location", "pathname"), + ) + def update_pathname(n_clicks, current_pathname): + if n_clicks is not None: + return "/new/pathname" + + return current_pathname + + dash_dcc.start_server(app) + + dash_dcc.percy_snapshot("link -- location") + + # Check that link updates pathname + dash_dcc.find_element("#test-link").click() + until( + lambda: dash_dcc.driver.current_url.replace("http://localhost:8050", "") + == "/test/pathname", + 3, + ) + dash_dcc.wait_for_text_to_equal("#test-pathname", "/test/pathname") + + # Check that hash is updated in the Location + dash_dcc.find_element("#test-link-hash").click() + dash_dcc.wait_for_text_to_equal("#test-pathname", "/test/pathname") + dash_dcc.wait_for_text_to_equal("#test-hash", "#test") + dash_dcc.percy_snapshot("link -- /test/pathname#test") + + # Check that search is updated in the Location + # note that this goes through href and therefore wipes the hash + dash_dcc.find_element("#test-link-search").click() + dash_dcc.wait_for_text_to_equal("#test-search", "?testQuery=testValue") + dash_dcc.wait_for_text_to_equal("#test-hash", "") + dash_dcc.percy_snapshot("link -- /test/pathname?testQuery=testValue") + + # Check that pathname is updated through a Button click via props + dash_dcc.find_element("#test-button").click() + dash_dcc.wait_for_text_to_equal("#test-pathname", "/new/pathname") + dash_dcc.wait_for_text_to_equal("#test-search", "?testQuery=testValue") + dash_dcc.percy_snapshot("link -- /new/pathname?testQuery=testValue") + + # Check that pathname is updated through an a tag click via props + dash_dcc.find_element("#test-a").click() + + dash_dcc.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") + dash_dcc.wait_for_text_to_equal("#test-search", "") + dash_dcc.wait_for_text_to_equal("#test-hash", "") + dash_dcc.percy_snapshot("link -- /test/pathname/a") + + # Check that hash is updated through an a tag click via props + dash_dcc.find_element("#test-a-hash").click() + dash_dcc.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") + dash_dcc.wait_for_text_to_equal("#test-search", "") + dash_dcc.wait_for_text_to_equal("#test-hash", "#test-hash") + dash_dcc.percy_snapshot("link -- /test/pathname/a#test-hash") + + # Check that hash is updated through an a tag click via props + dash_dcc.find_element("#test-a-query").click() + dash_dcc.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") + dash_dcc.wait_for_text_to_equal("#test-search", "?queryA=valueA") + dash_dcc.wait_for_text_to_equal("#test-hash", "") + dash_dcc.percy_snapshot("link -- /test/pathname/a?queryA=valueA") diff --git a/tests/test_integration_1.py b/tests/test_integration_1.py index 011d6a9c0..b202f9d0f 100644 --- a/tests/test_integration_1.py +++ b/tests/test_integration_1.py @@ -4,7 +4,7 @@ from multiprocessing import Lock import time import dash -from dash.dependencies import Input, Output, State +from dash.dependencies import Input, Output import dash_html_components as html import dash_core_components as dcc from selenium.webdriver.common.by import By @@ -409,139 +409,3 @@ def render_content(tab): self.wait_for_text_to_equal("#tabs-content", "Default selected Tab content 1") self.snapshot("Tab 1 should be selected by default") - - def test_location_link(self): - app = dash.Dash(__name__) - - app.layout = html.Div( - [ - html.Div(id="waitfor"), - dcc.Location(id="test-location", refresh=False), - dcc.Link( - html.Button("I am a clickable button"), - id="test-link", - href="/test/pathname", - ), - dcc.Link( - html.Button("I am a clickable hash button"), - id="test-link-hash", - href="#test", - ), - dcc.Link( - html.Button("I am a clickable search button"), - id="test-link-search", - href="?testQuery=testValue", - refresh=False, - ), - html.Button( - "I am a magic button that updates pathname", id="test-button" - ), - html.A("link to click", href="/test/pathname/a", id="test-a"), - html.A("link to click", href="#test-hash", id="test-a-hash"), - html.A("link to click", href="?queryA=valueA", id="test-a-query"), - html.Div(id="test-pathname", children=[]), - html.Div(id="test-hash", children=[]), - html.Div(id="test-search", children=[]), - ] - ) - - @app.callback( - Output(component_id="test-pathname", component_property="children"), - [Input(component_id="test-location", component_property="pathname")], - ) - def update_test_pathname(pathname): - return pathname - - @app.callback( - Output(component_id="test-hash", component_property="children"), - [Input(component_id="test-location", component_property="hash")], - ) - def update_test_hash(hash_val): - if hash_val is None: - return "" - - return hash_val - - @app.callback( - Output(component_id="test-search", component_property="children"), - [Input(component_id="test-location", component_property="search")], - ) - def update_test_search(search): - if search is None: - return "" - - return search - - @app.callback( - Output(component_id="test-location", component_property="pathname"), - [Input(component_id="test-button", component_property="n_clicks")], - [State(component_id="test-location", component_property="pathname")], - ) - def update_pathname(n_clicks, current_pathname): - if n_clicks is not None: - return "/new/pathname" - - return current_pathname - - self.startServer(app=app) - - time.sleep(1) - self.snapshot("link -- location") - - # Check that link updates pathname - self.wait_for_element_by_css_selector("#test-link").click() - self.assertEqual( - self.driver.current_url.replace("http://localhost:8050", ""), - "/test/pathname", - ) - self.wait_for_text_to_equal("#test-pathname", "/test/pathname") - - # Check that hash is updated in the Location - self.wait_for_element_by_css_selector("#test-link-hash").click() - self.wait_for_text_to_equal("#test-pathname", "/test/pathname") - self.wait_for_text_to_equal("#test-hash", "#test") - self.snapshot("link -- /test/pathname#test") - - # Check that search is updated in the Location -- note that this goes through href and therefore wipes the hash - self.wait_for_element_by_css_selector("#test-link-search").click() - self.wait_for_text_to_equal("#test-search", "?testQuery=testValue") - self.wait_for_text_to_equal("#test-hash", "") - self.snapshot("link -- /test/pathname?testQuery=testValue") - - # Check that pathname is updated through a Button click via props - self.wait_for_element_by_css_selector("#test-button").click() - self.wait_for_text_to_equal("#test-pathname", "/new/pathname") - self.wait_for_text_to_equal("#test-search", "?testQuery=testValue") - self.snapshot("link -- /new/pathname?testQuery=testValue") - - # Check that pathname is updated through an a tag click via props - self.wait_for_element_by_css_selector("#test-a").click() - try: - self.wait_for_element_by_css_selector("#waitfor") - except Exception as e: - print( - self.wait_for_element_by_css_selector( - "#_dash-app-content" - ).get_attribute("innerHTML") - ) - raise e - - self.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") - self.wait_for_text_to_equal("#test-search", "") - self.wait_for_text_to_equal("#test-hash", "") - self.snapshot("link -- /test/pathname/a") - - # Check that hash is updated through an a tag click via props - self.wait_for_element_by_css_selector("#test-a-hash").click() - self.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") - self.wait_for_text_to_equal("#test-search", "") - self.wait_for_text_to_equal("#test-hash", "#test-hash") - self.snapshot("link -- /test/pathname/a#test-hash") - - # Check that hash is updated through an a tag click via props - self.wait_for_element_by_css_selector("#test-a-query").click() - self.wait_for_element_by_css_selector("#waitfor") - self.wait_for_text_to_equal("#test-pathname", "/test/pathname/a") - self.wait_for_text_to_equal("#test-search", "?queryA=valueA") - self.wait_for_text_to_equal("#test-hash", "") - self.snapshot("link -- /test/pathname/a?queryA=valueA") diff --git a/tests/test_integration_2.py b/tests/test_integration_2.py index d224bf0e3..e186b4312 100644 --- a/tests/test_integration_2.py +++ b/tests/test_integration_2.py @@ -297,16 +297,6 @@ def on_click(n_clicks): self.driver.switch_to.alert.accept() - def test_user_supplied_css(self): - app = dash.Dash(__name__) - - app.layout = html.Div(className="test-input-css", children=[dcc.Input()]) - - self.startServer(app) - - self.wait_for_element_by_css_selector(".test-input-css") - self.snapshot("styled input - width: 100%, border-color: hotpink") - def test_logout_btn(self): app = dash.Dash(__name__) @@ -351,37 +341,6 @@ def _insert_cookie(rep): self.assertFalse(self.driver.get_cookie("logout-cookie")) - def test_simple_callback(self): - app = dash.Dash(__name__) - app.layout = html.Div( - [ - dcc.Input(id="input"), - html.Div(html.Div([1.5, None, "string", html.Div(id="output-1")])), - ] - ) - - call_count = Value("i", 0) - - @app.callback(Output("output-1", "children"), [Input("input", "value")]) - def update_output(value): - call_count.value = call_count.value + 1 - return value - - self.startServer(app) - - input1 = self.wait_for_element_by_css_selector("#input") - input1.send_keys("hello world") - output1 = self.wait_for_element_by_css_selector("#output-1") - self.wait_for_text_to_equal("#output-1", "hello world") - output1.click() # Lose focus, no callback sent for value. - - self.assertEqual( - call_count.value, - # an initial call to retrieve the first value - # plus one for each hello world character - 1 + len("hello world"), - ) - def test_disabled_tab(self): app = dash.Dash(__name__) app.layout = html.Div(