diff --git a/CHANGELOG.md b/CHANGELOG.md
index de24f7ab8..18a8e3f13 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,13 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
+## [Unreleased]
+### Changed
+- [#740](https://github.com/plotly/dash-core-components/pull/740) Keep components that are loading in the DOM, but not visible, as opposed to removing them entirely. This will ensure that the size of the component's container does not shrink or expand when the component goes into the loading state.
+
+### Fixed
+- [#740](https://github.com/plotly/dash-core-components/pull/740) Fixed bug in which mapbox `uirevision` was not behaving when inside a `dcc.Loading` component
+
## [1.9.0] - 2020-04-01
### Changed
- [#766](https://github.com/plotly/dash-core-components/pull/766) Update from React 16.8.6 to 16.13.0
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 000000000..b6302a609
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,5 @@
+[pytest]
+testpaths = tests/
+addopts = -rsxX -vv
+log_format = %(asctime)s | %(levelname)s | %(name)s:%(lineno)d | %(message)s
+log_cli_level = ERROR
diff --git a/src/components/Loading.react.js b/src/components/Loading.react.js
index 2077fbb9e..42e560843 100644
--- a/src/components/Loading.react.js
+++ b/src/components/Loading.react.js
@@ -5,7 +5,6 @@ import DefaultSpinner from '../fragments/Loading/spinners/DefaultSpinner.jsx';
import CubeSpinner from '../fragments/Loading/spinners/CubeSpinner.jsx';
import CircleSpinner from '../fragments/Loading/spinners/CircleSpinner.jsx';
import DotSpinner from '../fragments/Loading/spinners/DotSpinner.jsx';
-import {type} from 'ramda';
function getSpinner(spinnerType) {
switch (spinnerType) {
@@ -22,6 +21,19 @@ function getSpinner(spinnerType) {
}
}
+const hiddenContainer = {visibility: 'hidden', position: 'relative'};
+
+const coveringSpinner = {
+ visibility: 'visible',
+ position: 'absolute',
+ top: '0',
+ height: '100%',
+ width: '100%',
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+};
+
/**
* A Loading component that wraps any other component and displays a spinner until the wrapped component has rendered.
*/
@@ -37,27 +49,26 @@ export default class Loading extends Component {
type: spinnerType,
} = this.props;
- if (loading_state && loading_state.is_loading) {
- const Spinner = getSpinner(spinnerType);
- return (
-
- );
- }
-
- if (
- type(this.props.children) !== 'Object' ||
- type(this.props.children) !== 'Function'
- ) {
- return
{this.props.children}
;
- }
- return this.props.children;
+ const isLoading = loading_state && loading_state.is_loading;
+ const Spinner = isLoading && getSpinner(spinnerType);
+
+ return (
+
+ {this.props.children}
+
+ {isLoading && (
+
+ )}
+
+
+ );
}
}
@@ -85,27 +96,29 @@ Loading.propTypes = {
]),
/**
- * Property that determines which spinner to show - one of 'graph', 'cube', 'circle', 'dot', or 'default'.
+ * Property that determines which spinner to show
+ * one of 'graph', 'cube', 'circle', 'dot', or 'default'.
*/
type: PropTypes.oneOf(['graph', 'cube', 'circle', 'dot', 'default']),
/**
- * Boolean that determines if the loading spinner will be displayed full-screen or not
+ * Boolean that makes the spinner display full-screen
*/
fullscreen: PropTypes.bool,
/**
- * Boolean that determines if the loading spinner will display the status.prop_name and component_name
+ * If true, the spinner will display the component_name and prop_name
+ * while loading
*/
debug: PropTypes.bool,
/**
- * Additional CSS class for the root DOM node
+ * Additional CSS class for the spinner root DOM node
*/
className: PropTypes.string,
/**
- * Additional CSS styling for the root DOM node
+ * Additional CSS styling for the spinner root DOM node
*/
style: PropTypes.object,
diff --git a/src/fragments/Loading/spinners/CircleSpinner.jsx b/src/fragments/Loading/spinners/CircleSpinner.jsx
index c7d1369a3..ab53798ea 100644
--- a/src/fragments/Loading/spinners/CircleSpinner.jsx
+++ b/src/fragments/Loading/spinners/CircleSpinner.jsx
@@ -191,7 +191,7 @@ CircleSpinner.propTypes = {
color: PropTypes.string,
className: PropTypes.string,
fullscreen: PropTypes.bool,
- style: PropTypes.bool,
+ style: PropTypes.object,
debug: PropTypes.bool,
};
diff --git a/src/fragments/Loading/spinners/CubeSpinner.jsx b/src/fragments/Loading/spinners/CubeSpinner.jsx
index 73dd71c79..5736c321f 100644
--- a/src/fragments/Loading/spinners/CubeSpinner.jsx
+++ b/src/fragments/Loading/spinners/CubeSpinner.jsx
@@ -193,7 +193,7 @@ CubeSpinner.propTypes = {
color: PropTypes.string,
className: PropTypes.string,
fullscreen: PropTypes.bool,
- style: PropTypes.bool,
+ style: PropTypes.object,
debug: PropTypes.bool,
};
diff --git a/src/fragments/Loading/spinners/DefaultSpinner.jsx b/src/fragments/Loading/spinners/DefaultSpinner.jsx
index 959904682..5770b9142 100644
--- a/src/fragments/Loading/spinners/DefaultSpinner.jsx
+++ b/src/fragments/Loading/spinners/DefaultSpinner.jsx
@@ -116,7 +116,7 @@ DefaultSpinner.propTypes = {
color: PropTypes.string,
className: PropTypes.string,
fullscreen: PropTypes.bool,
- style: PropTypes.bool,
+ style: PropTypes.object,
debug: PropTypes.bool,
};
diff --git a/src/fragments/Loading/spinners/DotSpinner.jsx b/src/fragments/Loading/spinners/DotSpinner.jsx
index 7f41a1264..6cf81159d 100644
--- a/src/fragments/Loading/spinners/DotSpinner.jsx
+++ b/src/fragments/Loading/spinners/DotSpinner.jsx
@@ -95,7 +95,7 @@ DotSpinner.propTypes = {
color: PropTypes.string,
className: PropTypes.string,
fullscreen: PropTypes.bool,
- style: PropTypes.bool,
+ style: PropTypes.object,
debug: PropTypes.bool,
};
diff --git a/src/fragments/Loading/spinners/GraphSpinner.jsx b/src/fragments/Loading/spinners/GraphSpinner.jsx
index 5cc4ac5e2..50d32c462 100644
--- a/src/fragments/Loading/spinners/GraphSpinner.jsx
+++ b/src/fragments/Loading/spinners/GraphSpinner.jsx
@@ -332,7 +332,7 @@ GraphSpinner.propTypes = {
color: PropTypes.string,
className: PropTypes.string,
fullscreen: PropTypes.bool,
- style: PropTypes.bool,
+ style: PropTypes.object,
debug: PropTypes.bool,
};
diff --git a/tests/conftest.py b/tests/conftest.py
index 34b124ae1..516aed669 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -30,5 +30,6 @@ def dash_dcc(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"),
+ pause=request.config.getoption("pause"),
) as dc:
yield dc
diff --git a/tests/integration/graph/test_graph_basics.py b/tests/integration/graph/test_graph_basics.py
index 55d361e18..aac21da7f 100644
--- a/tests/integration/graph/test_graph_basics.py
+++ b/tests/integration/graph/test_graph_basics.py
@@ -93,7 +93,7 @@ def selected_df_figure(selection):
@pytest.mark.DCC672
-def test_grbs002_graph_wrapped_in_loading_component_does_not_fail(dash_dcc):
+def test_grbs003_graph_wrapped_in_loading_component_does_not_fail(dash_dcc):
app = dash.Dash(__name__, suppress_callback_exceptions=True)
app.layout = html.Div([
html.H1('subplot issue'),
@@ -101,7 +101,7 @@ def test_grbs002_graph_wrapped_in_loading_component_does_not_fail(dash_dcc):
dcc.Loading(id="page-content")
])
- @app.callback(Output('page-content', 'children'), [Input('url', 'value')])
+ @app.callback(Output('page-content', 'children'), [Input('url', 'pathname')])
def render_page(url):
return [
dcc.Dropdown(
diff --git a/tests/integration/loading/test_loading_component.py b/tests/integration/loading/test_loading_component.py
new file mode 100644
index 000000000..905100c11
--- /dev/null
+++ b/tests/integration/loading/test_loading_component.py
@@ -0,0 +1,280 @@
+from multiprocessing import Lock
+
+import dash
+from dash.dependencies import Input, Output
+import dash_core_components as dcc
+import dash_html_components as html
+
+
+def test_ldcp001_loading_component_initialization(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__)
+
+ app.layout = html.Div(
+ [dcc.Loading([html.Div(id="div-1")], className="loading")], id="root"
+ )
+
+ @app.callback(Output("div-1", "children"), [Input("root", "n_clicks")])
+ def updateDiv(children):
+ with lock:
+ return "content"
+
+ with lock:
+ dash_dcc.start_server(app)
+ dash_dcc.find_element(".loading .dash-spinner")
+ # ensure inner component is also mounted
+ dash_dcc.wait_for_text_to_equal("#div-1", "")
+
+ dash_dcc.wait_for_text_to_equal("#div-1", "content")
+
+ assert not dash_dcc.get_logs()
+
+
+def test_ldcp002_loading_component_action(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__)
+
+ app.layout = html.Div(
+ [dcc.Loading([html.Div(id="div-1")], className="loading")], id="root"
+ )
+
+ @app.callback(Output("div-1", "children"), [Input("root", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is not None:
+ with lock:
+ return "changed"
+
+ return "content"
+
+ with lock:
+ dash_dcc.start_server(app)
+ dash_dcc.wait_for_text_to_equal("#div-1", "content")
+
+ dash_dcc.find_element("#root").click()
+
+ dash_dcc.find_element(".loading .dash-spinner")
+ # mounted but hidden, so looks like no text
+ dash_dcc.wait_for_text_to_equal("#div-1", "")
+
+ dash_dcc.wait_for_text_to_equal("#div-1", "changed")
+
+ assert not dash_dcc.get_logs()
+
+
+def test_ldcp003_multiple_loading_components(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__)
+
+ app.layout = html.Div(
+ [
+ dcc.Loading([html.Button(id="btn-1")], className="loading-1"),
+ dcc.Loading([html.Button(id="btn-2")], className="loading-2"),
+ ],
+ id="root",
+ )
+
+ @app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is not None:
+ with lock:
+ return "changed 1"
+
+ return "content 1"
+
+ @app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is not None:
+ with lock:
+ return "changed 2"
+
+ return "content 2"
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.wait_for_text_to_equal("#btn-1", "content 1")
+ dash_dcc.wait_for_text_to_equal("#btn-2", "content 2")
+
+ with lock:
+ dash_dcc.find_element("#btn-1").click()
+
+ dash_dcc.find_element(".loading-2 .dash-spinner")
+ dash_dcc.wait_for_text_to_equal("#btn-2", "")
+
+ dash_dcc.wait_for_text_to_equal("#btn-2", "changed 2")
+
+ with lock:
+ dash_dcc.find_element("#btn-2").click()
+
+ dash_dcc.find_element(".loading-1 .dash-spinner")
+ dash_dcc.wait_for_text_to_equal("#btn-1", "")
+
+ dash_dcc.wait_for_text_to_equal("#btn-1", "changed 1")
+
+ assert not dash_dcc.get_logs()
+
+
+def test_ldcp004_nested_loading_components(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__)
+
+ app.layout = html.Div(
+ [
+ dcc.Loading(
+ [
+ html.Button(id="btn-1"),
+ dcc.Loading([html.Button(id="btn-2")], className="loading-2"),
+ ],
+ className="loading-1",
+ )
+ ],
+ id="root",
+ )
+
+ @app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is not None:
+ with lock:
+ return "changed 1"
+
+ return "content 1"
+
+ @app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is not None:
+ with lock:
+ return "changed 2"
+
+ return "content 2"
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.wait_for_text_to_equal("#btn-1", "content 1")
+ dash_dcc.wait_for_text_to_equal("#btn-2", "content 2")
+
+ with lock:
+ dash_dcc.find_element("#btn-1").click()
+
+ dash_dcc.find_element(".loading-2 .dash-spinner")
+ dash_dcc.wait_for_text_to_equal("#btn-2", "")
+
+ dash_dcc.wait_for_text_to_equal("#btn-2", "changed 2")
+
+ with lock:
+ dash_dcc.find_element("#btn-2").click()
+
+ dash_dcc.find_element(".loading-1 .dash-spinner")
+ dash_dcc.wait_for_text_to_equal("#btn-1", "")
+
+ dash_dcc.wait_for_text_to_equal("#btn-1", "changed 1")
+
+ assert not dash_dcc.get_logs()
+
+
+def test_ldcp005_dynamic_loading_component(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__, suppress_callback_exceptions=True)
+
+ app.layout = html.Div([html.Button(id="btn-1"), html.Div(id="div-1")])
+
+ @app.callback(Output("div-1", "children"), [Input("btn-1", "n_clicks")])
+ def updateDiv(n_clicks):
+ if n_clicks is None:
+ return
+
+ with lock:
+ return html.Div(
+ [
+ html.Button(id="btn-2"),
+ dcc.Loading([html.Button(id="btn-3")], className="loading-1"),
+ ]
+ )
+
+ @app.callback(Output("btn-3", "children"), [Input("btn-2", "n_clicks")])
+ def updateDynamic(n_clicks):
+ if n_clicks is None:
+ return "content"
+
+ with lock:
+ return "changed"
+
+ dash_dcc.start_server(app)
+
+ dash_dcc.find_element("#btn-1")
+ dash_dcc.wait_for_text_to_equal("#div-1", "")
+
+ dash_dcc.find_element("#btn-1").click()
+
+ dash_dcc.find_element("#div-1 #btn-2")
+ dash_dcc.wait_for_text_to_equal("#btn-3", "content")
+
+ with lock:
+ dash_dcc.find_element("#btn-2").click()
+
+ dash_dcc.find_element(".loading-1 .dash-spinner")
+ dash_dcc.wait_for_text_to_equal("#btn-3", "")
+
+ dash_dcc.wait_for_text_to_equal("#btn-3", "changed")
+
+ assert not dash_dcc.get_logs()
+
+
+def test_ldcp006_children_identity(dash_dcc):
+ lock = Lock()
+
+ app = dash.Dash(__name__)
+ app.layout = html.Div(
+ [
+ html.Button("click", id="btn"),
+ dcc.Loading(dcc.Graph(id="graph"), className="loading"),
+ ]
+ )
+
+ @app.callback(Output("graph", "figure"), [Input("btn", "n_clicks")])
+ def update_graph(n):
+ with lock:
+ bars = list(range(2, (n or 0) + 5))
+ return {
+ "data": [{"type": "bar", "x": bars, "y": bars}],
+ "layout": {"width": 400, "height": 400},
+ }
+
+ def get_graph_visibility():
+ return dash_dcc.driver.execute_script(
+ "var gd_ = document.querySelector('.js-plotly-plot');"
+ "return getComputedStyle(gd_).visibility;"
+ )
+
+ with lock:
+ dash_dcc.start_server(app)
+ dash_dcc.find_element(".loading .dash-spinner")
+ dash_dcc.find_element("#graph .js-plotly-plot")
+ dash_dcc.driver.execute_script(
+ "window.gd = document.querySelector('.js-plotly-plot');"
+ "window.gd.__test__ = 'boo';"
+ )
+ assert get_graph_visibility() == "hidden"
+
+ test_identity = (
+ "var gd_ = document.querySelector('.js-plotly-plot');"
+ "return gd_ === window.gd && gd_.__test__ === 'boo';"
+ )
+
+ assert len(dash_dcc.find_elements(".js-plotly-plot .bars path")) == 3
+ assert dash_dcc.driver.execute_script(test_identity)
+ assert get_graph_visibility() == "visible"
+
+ with lock:
+ dash_dcc.find_element("#btn").click()
+ dash_dcc.find_element(".loading .dash-spinner")
+ assert len(dash_dcc.find_elements(".js-plotly-plot .bars path")) == 3
+ assert dash_dcc.driver.execute_script(test_identity)
+ assert get_graph_visibility() == "hidden"
+
+ assert len(dash_dcc.find_elements(".js-plotly-plot .bars path")) == 4
+ assert dash_dcc.driver.execute_script(test_identity)
+ assert get_graph_visibility() == "visible"
diff --git a/tests/integration/store/test_component_props.py b/tests/integration/store/test_component_props.py
index 15587ad68..e0095412b 100644
--- a/tests/integration/store/test_component_props.py
+++ b/tests/integration/store/test_component_props.py
@@ -4,17 +4,18 @@
import dash
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
+import dash.testing.wait as wait
import dash_core_components as dcc
import dash_html_components as html
-def test_stcp100_clear_data_on_all_types(store_app, dash_dcc):
+def test_stcp001_clear_data_on_all_types(store_app, dash_dcc):
dash_dcc.start_server(store_app)
assert dash_dcc.wait_for_contains_text("#output", store_app.uuid)
dash_dcc.multiple_click("#btn", 3)
- assert dash_dcc.get_local_storage() == {"n_clicks": 3}
+ wait.until(lambda: dash_dcc.get_local_storage() == {"n_clicks": 3}, timeout=1)
# button click sets clear_data=True on all type of stores
dash_dcc.find_element("#clear-btn").click()
@@ -28,7 +29,7 @@ def test_stcp100_clear_data_on_all_types(store_app, dash_dcc):
), "set clear_data=True should clear all data in three storage types"
-def test_stcp200_modified_ts(store_app, dash_dcc):
+def test_stcp002_modified_ts(store_app, dash_dcc):
app = dash.Dash(__name__)
app.layout = html.Div(
[
diff --git a/tests/integration/store/test_data_lifecycle.py b/tests/integration/store/test_data_lifecycle.py
index d42b76dd9..976547ee3 100644
--- a/tests/integration/store/test_data_lifecycle.py
+++ b/tests/integration/store/test_data_lifecycle.py
@@ -1,3 +1,6 @@
+import dash.testing.wait as wait
+
+
def test_stdl001_data_lifecycle_with_different_condition(store_app, dash_dcc):
dash_dcc.start_server(store_app)
@@ -31,7 +34,7 @@ def test_stdl001_data_lifecycle_with_different_condition(store_app, dash_dcc):
), "memory storage should contain the initial data in new tab"
dash_dcc.multiple_click("#btn", 2)
- assert dash_dcc.get_session_storage() == {"n_clicks": 2}
+ wait.until(lambda: dash_dcc.get_session_storage() == {"n_clicks": 2}, timeout=1)
assert (
'"n_clicks": 2' in dash_dcc.wait_for_element("#output").text
), "memory storage should reflect to the new clicks"
diff --git a/tests/test_integration_1.py b/tests/test_integration_1.py
index f7479d30b..7cedf563e 100644
--- a/tests/test_integration_1.py
+++ b/tests/test_integration_1.py
@@ -38,304 +38,6 @@ def snapshot(self, name):
print("Percy Snapshot {}".format(python_version))
self.percy_runner.snapshot(name=name)
- def test_loading_component_initialization(self):
- lock = Lock()
-
- app = dash.Dash(__name__)
-
- app.layout = html.Div([
- dcc.Loading([
- html.Div(id='div-1')
- ], className='loading')
- ], id='root')
-
- @app.callback(
- Output('div-1', 'children'),
- [Input('root', 'n_clicks')]
- )
- def updateDiv(children):
- with lock:
- return 'content'
-
- with lock:
- self.startServer(app)
- self.wait_for_element_by_css_selector(
- '.loading .dash-spinner'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading #div-1'
- )
-
- for entry in self.get_log():
- raise Exception('browser error logged during test', entry)
-
- def test_loading_component_action(self):
- lock = Lock()
-
- app = dash.Dash(__name__)
-
- app.layout = html.Div([
- dcc.Loading([
- html.Div(id='div-1')
- ], className='loading')
- ], id='root')
-
- @app.callback(
- Output('div-1', 'children'),
- [Input('root', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is not None:
- with lock:
- return
-
- return 'content'
-
- with lock:
- self.startServer(app)
- self.wait_for_element_by_css_selector(
- '.loading #div-1'
- )
-
- self.driver.find_element_by_id('root').click()
-
- self.wait_for_element_by_css_selector(
- '.loading .dash-spinner'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading #div-1'
- )
-
- for entry in self.get_log():
- raise Exception('browser error logged during test', entry)
-
- def test_multiple_loading_components(self):
- lock = Lock()
-
- app = dash.Dash(__name__)
-
- app.layout = html.Div([
- dcc.Loading([
- html.Button(id='btn-1')
- ], className='loading-1'),
- dcc.Loading([
- html.Button(id='btn-2')
- ], className='loading-2')
- ], id='root')
-
- @app.callback(
- Output('btn-1', 'value'),
- [Input('btn-2', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is not None:
- with lock:
- return
-
- return 'content'
-
- @app.callback(
- Output('btn-2', 'value'),
- [Input('btn-1', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is not None:
- with lock:
- return
-
- return 'content'
-
- self.startServer(app)
-
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- with lock:
- self.driver.find_element_by_id('btn-1').click()
-
- self.wait_for_element_by_css_selector(
- '.loading-2 .dash-spinner'
- )
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- with lock:
- self.driver.find_element_by_id('btn-2').click()
-
- self.wait_for_element_by_css_selector(
- '.loading-1 .dash-spinner'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- for entry in self.get_log():
- raise Exception('browser error logged during test', entry)
-
- def test_nested_loading_components(self):
- lock = Lock()
-
- app = dash.Dash(__name__)
-
- app.layout = html.Div([
- dcc.Loading([
- html.Button(id='btn-1'),
- dcc.Loading([
- html.Button(id='btn-2')
- ], className='loading-2')
- ], className='loading-1')
- ], id='root')
-
- @app.callback(
- Output('btn-1', 'value'),
- [Input('btn-2', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is not None:
- with lock:
- return
-
- return 'content'
-
- @app.callback(
- Output('btn-2', 'value'),
- [Input('btn-1', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is not None:
- with lock:
- return
-
- return 'content'
-
- self.startServer(app)
-
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- with lock:
- self.driver.find_element_by_id('btn-1').click()
-
- self.wait_for_element_by_css_selector(
- '.loading-2 .dash-spinner'
- )
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- with lock:
- self.driver.find_element_by_id('btn-2').click()
-
- self.wait_for_element_by_css_selector(
- '.loading-1 .dash-spinner'
- )
-
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-1'
- )
- self.wait_for_element_by_css_selector(
- '.loading-2 #btn-2'
- )
-
- for entry in self.get_log():
- raise Exception('browser error logged during test', entry)
-
- def test_dynamic_loading_component(self):
- lock = Lock()
-
- app = dash.Dash(__name__)
- app.config['suppress_callback_exceptions'] = True
-
- app.layout = html.Div([
- html.Button(id='btn-1'),
- html.Div(id='div-1')
- ])
-
- @app.callback(
- Output('div-1', 'children'),
- [Input('btn-1', 'n_clicks')]
- )
- def updateDiv(n_clicks):
- if n_clicks is None:
- return
-
- with lock:
- return html.Div([
- html.Button(id='btn-2'),
- dcc.Loading([
- html.Button(id='btn-3')
- ], className='loading-1')
- ])
-
- @app.callback(
- Output('btn-3', 'content'),
- [Input('btn-2', 'n_clicks')]
- )
- def updateDynamic(n_clicks):
- if n_clicks is None:
- return
-
- with lock:
- return 'content'
-
- self.startServer(app)
-
- self.wait_for_element_by_css_selector(
- '#btn-1'
- )
- self.wait_for_element_by_css_selector(
- '#div-1'
- )
-
- self.driver.find_element_by_id('btn-1').click()
-
- self.wait_for_element_by_css_selector(
- '#div-1 #btn-2'
- )
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-3'
- )
-
- with lock:
- self.driver.find_element_by_id('btn-2').click()
-
- self.wait_for_element_by_css_selector(
- '.loading-1 .dash-spinner'
- )
-
- self.wait_for_element_by_css_selector(
- '#div-1 #btn-2'
- )
- self.wait_for_element_by_css_selector(
- '.loading-1 #btn-3'
- )
-
- for entry in self.get_log():
- raise Exception('browser error logged during test', entry)
-
def test_loading_slider(self):
lock = Lock()
diff --git a/tests/unit/Loading.test.js b/tests/unit/Loading.test.js
index d9d716e10..9442376d1 100644
--- a/tests/unit/Loading.test.js
+++ b/tests/unit/Loading.test.js
@@ -14,7 +14,12 @@ test('Loading renders', () => {
);
- expect(loading.html()).toMatchSnapshot('Loading with is_loading=true');
+ expect(
+ loading
+ .find('.dash-spinner')
+ .parent()
+ .html()
+ ).toMatchSnapshot('Loading with is_loading=true');
});
test('Loading renders without loading_state', () => {
const loading = render(
@@ -23,7 +28,7 @@ test('Loading renders without loading_state', () => {
);
- expect(loading.html()).toEqual('Loading is done!
');
+ expect(loading.html()).toEqual('Loading is done!
');
});
test('Loading renders without loading_state.is_loading', () => {
const statusMock = {
@@ -36,7 +41,7 @@ test('Loading renders without loading_state.is_loading', () => {
);
- expect(loading.html()).toEqual('Loading is done!
');
+ expect(loading.html()).toEqual('Loading is done!
');
});
test('Loading renders without prop_name', () => {
const statusMock = {
@@ -49,7 +54,12 @@ test('Loading renders without prop_name', () => {
);
- expect(loading.html()).toMatchSnapshot('Loading with is_loading=true');
+ expect(
+ loading
+ .find('.dash-spinner')
+ .parent()
+ .html()
+ ).toMatchSnapshot('Loading with is_loading=true');
});
test('Loading renders without loading_state.component_name', () => {
const statusMock = {
@@ -62,7 +72,12 @@ test('Loading renders without loading_state.component_name', () => {
);
- expect(loading.html()).toMatchSnapshot('Loading with is_loading=true');
+ expect(
+ loading
+ .find('.dash-spinner')
+ .parent()
+ .html()
+ ).toMatchSnapshot('Loading with is_loading=true');
});
test('Loading renders with multiple children', () => {
const statusMock = {
@@ -78,7 +93,12 @@ test('Loading renders with multiple children', () => {
);
- expect(loading.html()).toMatchSnapshot('Loading with is_loading=true');
+ expect(
+ loading
+ .find('.dash-spinner')
+ .parent()
+ .html()
+ ).toMatchSnapshot('Loading with is_loading=true');
});
test("Loading checks all it's children for a loading_state", () => {
diff --git a/tests/unit/__snapshots__/Loading.test.js.snap b/tests/unit/__snapshots__/Loading.test.js.snap
index 29d3a8c2b..84f1bea22 100644
--- a/tests/unit/__snapshots__/Loading.test.js.snap
+++ b/tests/unit/__snapshots__/Loading.test.js.snap
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`Loading checks all it's children for a loading_state: Loading spinner for children 1`] = `"Child 1
Child 2
Child 3
"`;
+exports[`Loading checks all it's children for a loading_state: Loading spinner for children 1`] = `"Child 1
Child 2
Child 3
"`;
exports[`Loading renders with multiple children: Loading with is_loading=true 1`] = `
"