Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Improvements

* Add support for selecting menu items in `Navset` controllers to improve dropdown navigation test coverage. (#2066)

* `input_date()`, `input_date_range()`, `update_date()`, and `update_date_range()` now supports `""` for values, mins, and maxes. In this case, no date will be specified on the client. (#1713) (#1689)

* Restricted the allowable types of the `choices` parameter of `input_select()`, `input_selectize()`, `update_select()`, and `update_selectize()` to actual set of allowable types (previously, the type was suggesting HTML-like values were supported). (#2048)
Expand Down
24 changes: 24 additions & 0 deletions shiny/playwright/controller/_navs.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,35 @@ def click(self, *, timeout: Timeout = None) -> None:
"""
Clicks the nav panel.

If the nav panel is inside a dropdown, playwright will first open the dropdown before selecting the nav panel.

Parameters
----------
timeout
The maximum time to wait for the nav panel to be visible and interactable. Defaults to `None`.
"""

parent_ul_loc = self.loc.locator("..").locator("..")

parent_ul_cls = parent_ul_loc.element_handle().get_attribute("class")
cls_menu_regex = re.compile(rf"(^|\s+){re.escape('dropdown-menu')}(\s+|$)")
cls_show_regex = re.compile(rf"(^|\s+){re.escape('show')}(\s+|$)")
cls_dropdown_regex = re.compile(rf"(^|\s+){re.escape('dropdown')}(\s+|$)")

# If the item is in a dropdown and the dropdown is closed
if (
parent_ul_cls
and cls_menu_regex.search(parent_ul_cls)
and not cls_show_regex.search(parent_ul_cls)
):
grandparent_li_loc = parent_ul_loc.locator("..")
gnd_li_cls = grandparent_li_loc.element_handle().get_attribute("class")

# Confirm it is a dropdown
if gnd_li_cls and cls_dropdown_regex.search(gnd_li_cls):
# click the grandparent list item to open it before clicking the target item
grandparent_li_loc.click()

self.loc.click(timeout=timeout)

def expect_active(self, value: bool, *, timeout: Timeout = None) -> None:
Expand Down
52 changes: 52 additions & 0 deletions tests/playwright/shiny/components/nav/navset_menu/app-express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from shiny.express import input, render, ui

with ui.navset_pill(id="selected_navset_pill"):
with ui.nav_panel("A"):
"Panel A content"

with ui.nav_panel("B"):
"Panel B content"

with ui.nav_panel("C"):
"Panel C content"

with ui.nav_menu("Other links"):
with ui.nav_panel("D"):
"Page D content"

"----"
"Description:"
with ui.nav_control():
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
ui.h5("Selected:")


@render.code
def _():
return input.selected_navset_pill()


with ui.navset_underline(id="selected_navset_underline"):
with ui.nav_panel("A"):
"Panel A content"

with ui.nav_panel("B"):
"Panel B content"

with ui.nav_panel("C"):
"Panel C content"

with ui.nav_menu("Other links"):
with ui.nav_panel("D"):
"Page D content"

"----"
"Description:"
with ui.nav_control():
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
ui.h5("Selected:")


@render.code
def _underline():
return input.selected_navset_underline()
39 changes: 39 additions & 0 deletions tests/playwright/shiny/components/nav/navset_menu/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from typing import Union

import pytest
from playwright.sync_api import Page

from shiny.playwright import controller
from shiny.pytest import create_app_fixture
from shiny.run import ShinyAppProc

app = create_app_fixture(["app-express.py"])


@pytest.mark.parametrize(
"nav_factory,nav_id,out_id",
[
(controller.NavsetPill, "selected_navset_pill", "_"),
(controller.NavsetUnderline, "selected_navset_underline", "_underline"),
],
ids=["pill", "underline"],
)
def test_navset_menu(
page: Page,
app: ShinyAppProc,
nav_factory: Union[type[controller.NavsetPill], type[controller.NavsetUnderline]],
nav_id: str,
out_id: str,
):

page.goto(app.url)
navset = nav_factory(page, nav_id)
output: controller.OutputText = controller.OutputText(page, out_id)

navset.expect_value("A")
output.expect_value("A")

for panel in ["B", "C", "D"]:
navset.set(panel)
navset.expect_value(panel)
output.expect_value(panel)
Loading