diff --git a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py index e7c52eb6d..9fae41336 100644 --- a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py @@ -73,7 +73,7 @@ def callback_context_export_data(request): "active_cell": CallbackTriggerDict( id="vizro_table", property="active_cell", - value={'row': 0, 'column': 0, 'column_id': 'country'}, + value={"row": 0, "column": 0, "column_id": "country"}, str_id="vizro_table", triggered=False, ), @@ -81,17 +81,20 @@ def callback_context_export_data(request): id="vizro_table", property="derived_viewport_data", value=[ - {'country': 'Algeria', 'continent': 'Africa', 'year': 2007, 'lifeExp': 72.301, 'pop': 33333216, - 'gdpPercap': 6223.367465, 'iso_alpha': 'DZA', 'iso_num': 12}, - {'country': 'Egypt', 'continent': 'Africa', 'year': 2007, 'lifeExp': 71.33800000000002, - 'pop': 80264543, 'gdpPercap': 5581.180998, 'iso_alpha': 'EGY', 'iso_num': 818}, - {'country': 'South Africa', 'continent': 'Africa', 'year': 2007, 'lifeExp': 49.339, - 'pop': 43997828, - 'gdpPercap': 9269.657808, 'iso_alpha': 'ZAF', 'iso_num': 710} + { + "country": "Algeria", + "continent": "Africa", + "year": 2007, + }, + { + "country": "Egypt", + "continent": "Africa", + "year": 2007, + }, ], str_id="vizro_table", triggered=False, - ) + ), } ) mock_callback_context = { @@ -219,9 +222,7 @@ def test_invalid_target( @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( - "callback_context_export_data, " - "target_scatter_filter_and_filter_interaction, " - "target_box_filtered_pop", + "callback_context_export_data, " "target_scatter_filter_and_filter_interaction, " "target_box_filtered_pop", [ ( [["scatter_chart", "box_chart"], [10**6, 10**7], None, None], @@ -272,20 +273,14 @@ def test_multiple_targets_with_filter_and_filter_interaction( @pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_button") @pytest.mark.parametrize( - "callback_context_export_data, " - "target_scatter_filter_and_filter_interaction, " - "target_box_filtered_pop", + "callback_context_export_data, " "target_scatter_filter_and_filter_interaction, " "target_box_filtered_pop", [ ( [["scatter_chart", "box_chart"], [10**6, 10**7], None, "Algeria"], [[10**6, 10**7], None, ["Algeria"]], [10**6, 10**7], ), - ( - [["scatter_chart", "box_chart"], None, "Africa", "Algeria"], - [None, ["Africa"], ["Algeria"]], - None - ), + ([["scatter_chart", "box_chart"], None, "Africa", "Algeria"], [None, ["Africa"], ["Algeria"]], None), ( [["scatter_chart", "box_chart"], [10**6, 10**7], "Africa", "Algeria"], [[10**6, 10**7], ["Africa"], ["Algeria"]], @@ -330,4 +325,4 @@ def test_multiple_targets_with_filter_and_filter_interaction_and_table( ] == target_scatter_filter_and_filter_interaction.to_csv(index=False) assert result["download-dataframe_box_chart"]["filename"] == "box_chart.csv" - assert result["download-dataframe_box_chart"]["content"] == target_box_filtered_pop.to_csv(index=False) \ No newline at end of file + assert result["download-dataframe_box_chart"]["content"] == target_box_filtered_pop.to_csv(index=False) diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index ca999dfe4..3585fc465 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -2,6 +2,7 @@ from dash._callback_context import context_value from dash._utils import AttributeDict +import plotly.express as px import vizro.models as vm from vizro.actions import filter_interaction from vizro.actions._actions_utils import ( @@ -11,29 +12,64 @@ @pytest.fixture -def callback_context_click_continent(request): +def callback_context_filter_interaction(request): """Mock dash.callback_context that represents a click on a continent data-point.""" - continent = request.param + continent_filter_interaction, country_table_filter_interaction = request.param + + args_grouping_filter_interaction = [] + if continent_filter_interaction: + args_grouping_filter_interaction.append( + { + "clickData": CallbackTriggerDict( + id="box_chart", + property="clickData", + value={ + "points": [ + { + "customdata": [continent_filter_interaction], + } + ] + }, + str_id="box_chart", + triggered=False, + ) + } + ) + if country_table_filter_interaction: + args_grouping_filter_interaction.append( + { + "active_cell": CallbackTriggerDict( + id="vizro_table", + property="active_cell", + value={"row": 0, "column": 0, "column_id": "country"}, + str_id="vizro_table", + triggered=False, + ), + "derived_viewport_data": CallbackTriggerDict( + id="vizro_table", + property="derived_viewport_data", + value=[ + { + "country": country_table_filter_interaction, + "continent": "Africa", + "year": 2007, + }, + { + "country": "Egypt", + "continent": "Africa", + "year": 2007, + }, + ], + str_id="vizro_table", + triggered=False, + ), + } + ) + mock_callback_context = { "args_grouping": { "filters": [], - "filter_interaction": [ - { - "clickData": CallbackTriggerDict( - id="box_chart", - property="clickData", - value={ - "points": [ - { - "customdata": [continent], - } - ] - }, - str_id="box_chart", - triggered=False, - ) - } - ], + "filter_interaction": args_grouping_filter_interaction, "parameters": [], "theme_selector": CallbackTriggerDict( id="theme_selector", @@ -48,15 +84,48 @@ def callback_context_click_continent(request): return context_value -@pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") +@pytest.fixture +def target_scatter_filtered_continent(request, gapminder_2007, scatter_params): + continent, country = request.param + + data = gapminder_2007 + if continent: + data = data[data["continent"].isin([continent])] + if country: + data = data[data["country"].isin([country])] + + return px.scatter(data, **scatter_params).update_layout(margin_t=24) + + +@pytest.fixture +def target_box_filtered_continent(request, gapminder_2007, box_params): + continent, country = request.param + + data = gapminder_2007 + if continent: + data = data[data["continent"].isin([continent])] + if country: + data = data[data["country"].isin([country])] + + return px.box(data, **box_params).update_layout(margin_t=24) + + +@pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_button") class TestFilterInteraction: - @pytest.mark.parametrize("callback_context_click_continent", ["Africa", "Europe"], indirect=True) + @pytest.mark.parametrize( + "callback_context_filter_interaction", + [ + ("Africa", None), + ("Europe", None) + ], + indirect=True + ) def test_filter_interaction_without_targets_temporary_behavior( # temporary fix, see below test self, - callback_context_click_continent, + callback_context_filter_interaction, ): # Add action to relevant component - here component[0] is the source_chart - model_manager["test_page"].components[0].actions = [vm.Action(id="test_action", function=filter_interaction())] + model_manager["box_chart"].actions = [vm.Action(id="test_action", function=filter_interaction())] # Run action by picking the above added action function and executing it with () result = model_manager["test_action"].function() @@ -65,18 +134,25 @@ def test_filter_interaction_without_targets_temporary_behavior( # temporary fix @pytest.mark.xfail # This is the desired behavior, ie when no target is provided, then all charts filtered @pytest.mark.parametrize( - "callback_context_click_continent,target_scatter_filtered_continent,target_box_filtered_continent", - [("Africa", "Africa", "Africa"), ("Europe", "Europe", "Europe"), ("Americas", "Americas", "Americas")], + "callback_context_filter_interaction," + "target_scatter_filtered_continent," + "target_box_filtered_continent", + [ + + (("Africa", None), ("Africa", None), ("Africa", None)), + (("Europe", None), ("Europe", None), ("Europe", None)), + (("Americas", None), ("Americas", None), ("Americas", None)) + ], indirect=True, ) def test_filter_interaction_without_targets_desired_behavior( self, - callback_context_click_continent, + callback_context_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent, ): # Add action to relevant component - here component[0] is the source_chart - model_manager["test_page"].components[0].actions = [vm.Action(id="test_action", function=filter_interaction())] + model_manager["box_chart"].actions = [vm.Action(id="test_action", function=filter_interaction())] # Run action by picking the above added action function and executing it with () result = model_manager["test_action"].function() @@ -85,17 +161,21 @@ def test_filter_interaction_without_targets_desired_behavior( assert result["box_chart"] == target_box_filtered_continent @pytest.mark.parametrize( - "callback_context_click_continent,target_scatter_filtered_continent", - [("Africa", ["Africa"]), ("Europe", ["Europe"]), ("Americas", ["Americas"])], + "callback_context_filter_interaction,target_scatter_filtered_continent", + [ + (("Africa", None), ("Africa", None)), + (("Europe", None), ("Europe", None)), + (("Americas", None), ("Americas", None)) + ], indirect=True, ) def test_filter_interaction_with_one_target( self, - callback_context_click_continent, + callback_context_filter_interaction, target_scatter_filtered_continent, ): # Add action to relevant component - here component[0] is the source_chart - model_manager["test_page"].components[0].actions = [ + model_manager["box_chart"].actions = [ vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart"])) ] @@ -105,22 +185,22 @@ def test_filter_interaction_with_one_target( assert result["scatter_chart"] == target_scatter_filtered_continent @pytest.mark.parametrize( - "callback_context_click_continent,target_scatter_filtered_continent,target_box_filtered_continent", + "callback_context_filter_interaction,target_scatter_filtered_continent,target_box_filtered_continent", [ - ("Africa", ["Africa"], ["Africa"]), - ("Europe", ["Europe"], ["Europe"]), - ("Americas", ["Americas"], ["Americas"]), + (("Africa", None), ("Africa", None), ("Africa", None)), + (("Europe", None), ("Europe", None), ("Europe", None)), + (("Americas", None), ("Americas", None), ("Americas", None)), ], indirect=True, ) def test_filter_interaction_with_two_target( self, - callback_context_click_continent, + callback_context_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent, ): # Add action to relevant component - here component[0] is the source_chart - model_manager["test_page"].components[0].actions = [ + model_manager["box_chart"].actions = [ vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] @@ -132,18 +212,103 @@ def test_filter_interaction_with_two_target( @pytest.mark.xfail # This (or similar code) should raise a Value/Validation error explaining next steps @pytest.mark.parametrize("target", ["scatter_chart", ["scatter_chart"]]) - @pytest.mark.parametrize("callback_context_click_continent", ["Africa", "Europe"], indirect=True) + @pytest.mark.parametrize("callback_context_filter_interaction", [("Africa", None), ("Europe", None)], indirect=True) def test_filter_interaction_with_invalid_targets( self, target, - callback_context_click_continent, + callback_context_filter_interaction, ): with pytest.raises(ValueError, match="Target invalid_target not found in model_manager."): # Add action to relevant component - here component[0] is the source_chart - model_manager["test_page"].components[0].actions = [ + model_manager["box_chart"].actions = [ vm.Action(id="test_action", function=filter_interaction(targets=target)) ] + @pytest.mark.parametrize( + "callback_context_filter_interaction,target_scatter_filtered_continent", + [ + ((None, "Algeria"), (None, "Algeria")), + ((None, "Albania"), (None, "Albania")), + ((None, "Argentina"), (None, "Argentina")) + ], + indirect=True, + ) + def test_table_filter_interaction_with_one_target( + self, + callback_context_filter_interaction, + target_scatter_filtered_continent, + ): + model_manager["box_chart"].actions = [ + vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart"])) + ] + + model_manager["vizro_table"].actions = [ + vm.Action(function=filter_interaction(targets=["scatter_chart"])) + ] + + # Run action by picking the above added action function and executing it with () + result = model_manager["test_action"].function() + + assert result["scatter_chart"] == target_scatter_filtered_continent + + @pytest.mark.parametrize( + "callback_context_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent", + [ + ((None, "Algeria"), (None, "Algeria"), (None, "Algeria")), + ((None, "Albania"), (None, "Albania"), (None, "Albania")), + ((None, "Argentina"), (None, "Argentina"), (None, "Argentina")) + ], + indirect=True, + ) + def test_table_filter_interaction_with_two_targets( + self, + callback_context_filter_interaction, + target_scatter_filtered_continent, + target_box_filtered_continent, + ): + model_manager["box_chart"].actions = [ + vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + + model_manager["vizro_table"].actions = [ + vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + + # Run action by picking the above added action function and executing it with () + result = model_manager["test_action"].function() + + assert result["scatter_chart"] == target_scatter_filtered_continent + assert result["box_chart"] == target_box_filtered_continent + + @pytest.mark.parametrize( + "callback_context_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent", + [ + (("Africa", "Algeria"), ("Africa", "Algeria"), ("Africa", "Algeria")), + (("Europe", "Albania"), ("Europe", "Albania"), ("Europe", "Albania")), + (("Americas", "Argentina"), ("Americas", "Argentina"), ("Americas", "Argentina")) + ], + indirect=True, + ) + def test_mixed_chart_and_table_filter_interaction_with_two_targets( + self, + callback_context_filter_interaction, + target_scatter_filtered_continent, + target_box_filtered_continent, + ): + model_manager["box_chart"].actions = [ + vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + + model_manager["vizro_table"].actions = [ + vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + + # Run action by picking the above added action function and executing it with () + result = model_manager["test_action"].function() + + assert result["scatter_chart"] == target_scatter_filtered_continent + assert result["box_chart"] == target_box_filtered_continent + # TODO: Simplify parametrization, such that we have less repetitive code # TODO: Eliminate above xfails # TODO: Complement tests above with backend tests (currently the targets are also taken from model_manager!