diff --git a/vizro-core/tests/unit/vizro/models/_navigation/conftest.py b/vizro-core/tests/unit/vizro/models/_navigation/conftest.py index c250afd45..7ed2247b0 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/conftest.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/conftest.py @@ -99,7 +99,8 @@ def accordion_from_pages_as_dict(): @pytest.fixture -def navbar_div_default(): +def navbar_div_from_dict(accordion_from_page_as_list): + accordion = accordion_from_page_as_list return html.Div( children=[ html.Div( @@ -120,58 +121,52 @@ def navbar_div_default(): href="/", active=True, ), - dbc.Button( - id="nav_id_2", - children=[ - html.Div( - children=[ - html.Span("dashboard", className="material-symbols-outlined"), - html.Div(className="hidden"), - ], - className="nav-icon-text", - ), - None, - ], - className="icon-button", - href="/page-2", - active=False, - ), ], className="nav-bar", id="nav_bar_outer", ), - html.Div(hidden=True, id="nav_panel_outer"), + accordion, ] ) @pytest.fixture -def navbar_div_from_dict(accordion_from_page_as_list): - accordion = accordion_from_page_as_list - return html.Div( +def nav_item_default(): + return dbc.Button( + id="navitem", + children=[ + html.Div( + children=[html.Span("dashboard", className="material-symbols-outlined"), html.Div(className="hidden")], + className="nav-icon-text", + ), + None, + ], + className="icon-button", + href="/", + active=True, + ) + + +@pytest.fixture +def nav_item_with_optional(): + return dbc.Button( + id="navitem", children=[ html.Div( children=[ - dbc.Button( - id="nav_id_1", - children=[ - html.Div( - children=[ - html.Span("dashboard", className="material-symbols-outlined"), - html.Div(className="hidden"), - ], - className="nav-icon-text", - ), - None, - ], - className="icon-button", - href="/", - active=True, - ), + html.Span("home", className="material-symbols-outlined"), + html.Div(children=["This is "], className="icon-text"), ], - className="nav-bar", - id="nav_bar_outer", + className="nav-icon-text", ), - accordion, - ] + dbc.Tooltip( + children=html.P("This is a long text input"), + target="navitem", + placement="bottom", + className="custom-tooltip", + ), + ], + className="icon-button", + href="/", + active=True, ) diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py index 58a06b3ee..9c9fd3a2a 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_bar.py @@ -7,33 +7,38 @@ from pydantic import ValidationError import vizro.models as vm -from dash import html @pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") class TestNavBarInstantiation: - """Tests accordion model instantiation.""" + """Tests NavBar model instantiation.""" def test_navbar_mandatory_only(self): - navigation = vm.NavBar() - assert hasattr(navigation, "id") - assert navigation.pages == ["Page 1", "Page 2"] + nav_bar = vm.NavBar() + + assert hasattr(nav_bar, "id") + assert nav_bar.type == "navbar" + assert nav_bar.pages == ["Page 1", "Page 2"] def test_navbar_valid_pages_as_list(self, pages_as_list): nav_bar = vm.NavBar(pages=pages_as_list, id="nav_bar") assert nav_bar.id == "nav_bar" + assert nav_bar.type == "navbar" assert nav_bar.pages == pages_as_list def test_navbar_valid_pages_as_dict(self, pages_as_dict): nav_bar = vm.NavBar(pages=pages_as_dict, id="nav_bar") assert nav_bar.id == "nav_bar" + assert nav_bar.type == "navbar" assert nav_bar.pages == pages_as_dict def test_navbar_valid_pages_not_all_included(self): nav_bar = vm.NavBar(pages=["Page 1"], id="nav_bar") + assert nav_bar.id == "nav_bar" + assert nav_bar.type == "navbar" assert nav_bar.pages == ["Page 1"] def test_navbar_invalid_pages_empty_list(self): @@ -46,120 +51,47 @@ def test_navbar_invalid_pages_unknown_page(self): @pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -def test_navbar_build_default(navbar_div_default): - navbar = vm.NavBar() - - navbar.items[0].id = "nav_id_1" - navbar.items[1].id = "nav_id_2" - result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) - expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) - - assert result == expected - - -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -def test_navbar_build_pages_as_list(navbar_div_default): - navbar = vm.NavBar(pages=["Page 1", "Page 2"]) - - navbar.items[0].id = "nav_id_1" - navbar.items[1].id = "nav_id_2" - result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) - expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) - - assert result == expected - - -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -def test_navbar_build_pages_as_dict(navbar_div_from_dict): - navbar = vm.NavBar(pages={"Icon": ["Page 1", "Page 2"]}) - - navbar.items[0].id = "accordion_list" - result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) - - print("result>>>>", result) - expected = json.loads(json.dumps(navbar_div_from_dict, cls=plotly.utils.PlotlyJSONEncoder)) - - assert result == expected - - -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -def test_navbar_build_items(navbar_div_default): - navbar = vm.NavBar(items=[vm.NavItem(pages=["Page 1"]), vm.NavItem(pages=["Page 2"])]) - navbar.items[0].id = "nav_id_1" - navbar.items[1].id = "nav_id_2" +class TestNavBarBuildMethod: + """Tests NavBar model build method.""" - result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) - expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) + def test_navbar_build_default(self, navbar_div_default): + navbar = vm.NavBar() + navbar.items[0].id, navbar.items[1].id = "nav_id_1", "nav_id_2" - assert result == expected + result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) + assert result == expected -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -@pytest.mark.parametrize("pages", [(["Page 1", "Page 2"]), ([["Page 1"], ["Page 2"]])]) -def test_navbar_and_navigation_build_default(navbar_div_default, pages): - if isinstance(pages[0], str): + @pytest.mark.parametrize( + "pages", + [ + (["Page 1", "Page 2"]), # pages provided as list + ({"Icon 1": ["Page 1"], "Icon 2": ["Page 2"]}), # pages provided as dict + ], + ) + def test_navbar_build_pages(self, navbar_div_default, pages): navbar = vm.NavBar(pages=pages) - else: - navbar = vm.NavBar(items=[vm.NavItem(pages=pages[0]), vm.NavItem(pages=pages[1])]) - - navbar.items[0].id = "nav_id_1" - navbar.items[1].id = "nav_id_2" - result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) - expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) + navbar.items[0].id, navbar.items[1].id = "nav_id_1", "nav_id_2" + navbar.items[0].selector.id, navbar.items[1].selector.id = "accordion_list", "accordion_list" - assert result == expected + result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) + assert result == expected -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -@pytest.mark.parametrize( - "nav_pages, item_pages", - [ - (["Page 1", "Page 2"], [["Page 1"], ["Page 2"]]), - ( - {"title_1": ["Page 1", "Page 2"], "title_2": ["Page 1", "Page 2"]}, - [["Page 1", "Page 2"], ["Page 1", "Page 2"]], - ), - ], -) -def test_navbar_and_navigation_build_equality(nav_pages, item_pages, nav_id_1="nav_id_1", nav_id_2="nav_id_2"): - navbar_with_pages = vm.NavBar(pages=nav_pages) - navbar_with_items = vm.NavBar(items=[vm.NavItem(pages=item_pages[0]), vm.NavItem(pages=item_pages[1])]) - - navbar_with_pages.items[0].id, navbar_with_pages.items[1].id = nav_id_1, nav_id_2 - navbar_with_pages.items[0].selector.id, navbar_with_pages.items[1].selector.id = "accordion_1", "accordion_2" - - navbar_with_items.items[0].id, navbar_with_items.items[1].id = nav_id_1, nav_id_2 - navbar_with_items.items[0].selector.id, navbar_with_items.items[1].selector.id = "accordion_1", "accordion_2" - - navbar_with_pages = json.loads( - json.dumps(navbar_with_pages.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) - ) - navbar_with_items = json.loads( - json.dumps(navbar_with_items.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) + @pytest.mark.parametrize( + "nav_item_1, nav_item_2", + [ + (["Page 1"], ["Page 2"]), # NavItem pages provided as list + ({"Icon 1": ["Page 1"]}, {"Icon 2": ["Page 2"]}), # NavItem pages provided as dict + ], ) + def test_navbar_build_items(self, navbar_div_default, nav_item_1, nav_item_2): + navbar = vm.NavBar(items=[vm.NavItem(pages=nav_item_1), vm.NavItem(pages=nav_item_2)]) + navbar.items[0].id, navbar.items[1].id = "nav_id_1", "nav_id_2" - assert navbar_with_pages == navbar_with_items - - -@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") -@pytest.mark.parametrize( - "nav_pages, item_pages", - [ - (["Page 1"], ["Page 1"]), - ({"title_1": ["Page 1"]}, {"title_1": ["Page 1"]}), - ], -) -def test_navbar_build_equality(nav_pages, item_pages, nav_id="nav_id"): - navbar_with_pages = vm.NavBar(pages=nav_pages) - navbar_with_items = vm.NavBar(items=[vm.NavItem(pages=item_pages, id=nav_id)]) - - navbar_with_pages.items[0].id = nav_id - - navbar_with_pages = json.loads( - json.dumps(navbar_with_pages.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) - ) - navbar_with_items = json.loads( - json.dumps(navbar_with_items.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) - ) + result = json.loads(json.dumps(navbar.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) - assert navbar_with_pages == navbar_with_items + assert result == expected diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py new file mode 100644 index 000000000..d9e797b42 --- /dev/null +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_item.py @@ -0,0 +1,75 @@ +"""Unit tests for vizro.models.NavItem.""" +import json +import re + +import plotly +import pytest +from pydantic import ValidationError + +import vizro.models as vm + + +@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") +class TestNavItemInstantiation: + """Tests NavItem model instantiation.""" + + def test_navitem_mandatory_only(self): + nav_item = vm.NavItem(pages=["Page 1", "Page 2"]) + + assert nav_item.type == "navitem" + assert nav_item.pages == ["Page 1", "Page 2"] + assert nav_item.icon == "dashboard" + assert nav_item.max_text_length == 8 + assert hasattr(nav_item, "id") + assert hasattr(nav_item, "text") + assert hasattr(nav_item, "tooltip") + assert hasattr(nav_item, "selector") + + def test_navitem_mandatory_and_optional(self): + nav_item = vm.NavItem( + pages=["Page 1", "Page 2"], id="nav_item", icon="home", text="Homepage", tooltip="Homepage icon" + ) + + assert nav_item.id == "nav_item" + assert nav_item.type == "navitem" + assert nav_item.pages == ["Page 1", "Page 2"] + assert nav_item.icon == "home" + assert nav_item.text == "Homepage" + assert nav_item.tooltip == "Homepage icon" + + def test_navitem_text_validation(self): + nav_item = vm.NavItem(pages=["Page 1"], text="Homepage", tooltip="Homepage icon") + nav_item.set_text_and_tooltip() + assert nav_item.text == "Homepage" + assert nav_item.tooltip == "Homepage icon" + + def test_navitem_invalid_pages_empty_list(self): + with pytest.raises(ValidationError, match="Ensure this value has at least 1 item."): + vm.NavItem(pages=[], id="nav_item") + + def test_navitem_invalid_pages_unknown_page(self): + with pytest.raises(ValidationError, match=re.escape("Unknown page ID ['Test'] provided to argument 'pages'.")): + vm.NavItem(pages=["Test"], id="nav_item") + + +@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") +class TestNavItemBuildMethod: + """Tests NavItem model build method.""" + + def test_navitem_build_mandatory_only(self, nav_item_default): + nav_item = vm.NavItem(pages=["Page 1", "Page 2"]) + nav_item.id = "navitem" + + result = json.loads(json.dumps(nav_item.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(nav_item_default, cls=plotly.utils.PlotlyJSONEncoder)) + + assert result == expected + + def test_navitem_build_mandatory_and_optional(self, nav_item_with_optional): + nav_item = vm.NavItem(pages=["Page 1", "Page 2"], icon="home", text="This is a long text input") + nav_item.id = "navitem" + + result = json.loads(json.dumps(nav_item.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(nav_item_with_optional, cls=plotly.utils.PlotlyJSONEncoder)) + + assert result == expected diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py b/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py index f41e1d122..629dbe4d4 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py @@ -71,3 +71,71 @@ def test_navigation_build(pages): result = json.loads(json.dumps(navigation.build(), cls=plotly.utils.PlotlyJSONEncoder)) expected = json.loads(json.dumps(expected_navigation, cls=plotly.utils.PlotlyJSONEncoder)) assert result == expected + + +@pytest.mark.usefixtures("vizro_app", "dashboard_prebuild") +class TestNavigationBuildMethod: + """Tests navigation model build method.""" + + def test_navigation_build_default(self): + navigation = vm.Navigation() + accordion = Accordion(pages=["Page 1", "Page 2"]) + navigation.selector.id = accordion.id + + expected_navigation = html.Div(children=[html.Div(className="hidden", id="nav_bar_outer"), accordion.build()]) + result = json.loads(json.dumps(navigation.build(), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(expected_navigation, cls=plotly.utils.PlotlyJSONEncoder)) + assert result == expected + + @pytest.mark.parametrize("pages", [["Page 1", "Page 2"], {"Page 1": ["Page 1"], "Page 2": ["Page 2"]}]) + def test_navigation_build_pages(self, pages): + navigation = vm.Navigation(pages=pages) + accordion = Accordion(pages=pages) + navigation.selector.id = accordion.id + + expected_navigation = html.Div(children=[html.Div(className="hidden", id="nav_bar_outer"), accordion.build()]) + result = json.loads(json.dumps(navigation.build(), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(expected_navigation, cls=plotly.utils.PlotlyJSONEncoder)) + assert result == expected + + @pytest.mark.parametrize("pages", [["Page 1", "Page 2"], {"Icon 1": ["Page 1"], "Icon 2": ["Page 2"]}]) + def test_navigation_build_selector_accordion(self, pages): + nav_with_selector = vm.Navigation(selector=vm.Accordion(pages=pages)) + nav_with_pages = vm.Navigation(pages=pages) + nav_with_selector.selector.id = nav_with_pages.selector.id + + nav_with_selector = json.loads( + json.dumps(nav_with_selector.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) + ) + nav_with_pages = json.loads( + json.dumps(nav_with_pages.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder) + ) + + assert nav_with_selector == nav_with_pages + + @pytest.mark.parametrize("pages", [None, ["Page 1", "Page 2"], {"Icon 1": ["Page 1"], "Icon 2": ["Page 2"]}]) + def test_navigation_build_selector(self, navbar_div_default, pages): + navigation = vm.Navigation(pages=pages, selector=vm.NavBar()) + navigation.selector.items[0].id, navigation.selector.items[1].id = "nav_id_1", "nav_id_2" + + result = json.loads(json.dumps(navigation.build(active_page_id="Page 1"), cls=plotly.utils.PlotlyJSONEncoder)) + expected = json.loads(json.dumps(navbar_div_default, cls=plotly.utils.PlotlyJSONEncoder)) + + assert result == expected + + def test_navigation_all_options(self): + navigation_configs = [ + vm.Navigation(), + vm.Navigation(pages=["Page 1", "Page 2"]), + vm.Navigation(selector=vm.Accordion(pages=["Page 1", "Page 2"])), + vm.Navigation(selector=vm.Accordion(pages={"Accordion title": ["Page 1", "Page 2"]})), + vm.Navigation(pages=["Page 1", "Page 2"], selector=vm.NavBar()), + vm.Navigation(selector=vm.NavBar()), + vm.Navigation(selector=vm.NavBar(pages=["Page 1", "Page 2"])), + vm.Navigation(selector=vm.NavBar(items=[vm.NavItem(pages=["Page 1", "Page 2"])])), + vm.Navigation(selector=vm.NavBar(items=[vm.NavItem(pages={"Icon 1": ["Page 1", "Page 2"]})])), + ] + + for config in navigation_configs: + navigation = config.build(active_page_id="Page 1") + assert isinstance(navigation, html.Div)