diff --git a/components/dash-core-components/src/components/Loading.react.js b/components/dash-core-components/src/components/Loading.react.js index f703056c8b..827a39ca9b 100644 --- a/components/dash-core-components/src/components/Loading.react.js +++ b/components/dash-core-components/src/components/Loading.react.js @@ -44,6 +44,9 @@ const loadingSelector = (componentPath, targetComponents) => state => { if (!target) { return false; } + if (target === '*') { + return true; + } if (Array.isArray(target)) { return includes(l.property, target); } diff --git a/components/dash-core-components/tests/integration/loading/test_loading_component.py b/components/dash-core-components/tests/integration/loading/test_loading_component.py index 597bc396ff..8db1fe2971 100644 --- a/components/dash-core-components/tests/integration/loading/test_loading_component.py +++ b/components/dash-core-components/tests/integration/loading/test_loading_component.py @@ -755,3 +755,78 @@ def updateDiv2(n_clicks): assert spinners == [] assert dash_dcc.get_logs() == [] + + +# target_components with "*" wildcard - matches all props +def test_ldcp018_loading_component_target_components_wildcard(dash_dcc): + lock = Lock() + + app = Dash(__name__) + + app.layout = html.Div( + [ + dcc.Loading( + [ + html.Button("1", id="btn-1"), + html.Button("2", id="btn-2"), + html.Button("3", id="btn-3"), + ], + className="loading-1", + target_components={"btn-2": "*"}, + ) + ], + id="root", + ) + + @app.callback(Output("btn-2", "children"), [Input("btn-1", "n_clicks")]) + def update_btn2_children(n_clicks): + if n_clicks: + with lock: + return "changed children" + return "content 2" + + @app.callback(Output("btn-2", "className"), [Input("btn-3", "n_clicks")]) + def update_btn2_class(n_clicks): + if n_clicks: + with lock: + return "changed-class" + return "original-class" + + @app.callback(Output("btn-1", "children"), [Input("btn-2", "n_clicks")]) + def update_btn1_children(n_clicks): + if n_clicks: + with lock: + return "changed 1" + return "content 1" + + 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") + + # Test that updating btn-2 children triggers spinner (wildcard matches) + with lock: + dash_dcc.find_element("#btn-1").click() + dash_dcc.wait_for_element(".loading-1 .dash-spinner") + dash_dcc.wait_for_text_to_equal("#btn-2", "") + + dash_dcc.wait_for_text_to_equal("#btn-2", "changed children") + + # Test that updating btn-2 className also triggers spinner (wildcard matches) + with lock: + dash_dcc.find_element("#btn-3").click() + dash_dcc.wait_for_element(".loading-1 .dash-spinner") + + # Verify className was updated + dash_dcc.wait_for_class_to_equal("#btn-2", "changed-class") + + # Test that updating btn-1 does NOT trigger spinner (not targeted) + with lock: + dash_dcc.find_element("#btn-2").click() + spinners = dash_dcc.find_elements(".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 spinners == [] + + assert dash_dcc.get_logs() == []