Skip to content

Commit a380aff

Browse files
authored
fix(test): Add ability to select ui.nav_menu controllers (#2066)
1 parent bee4570 commit a380aff

File tree

4 files changed

+117
-0
lines changed

4 files changed

+117
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3333

3434
### Improvements
3535

36+
* Add support for selecting menu items in `Navset` controllers to improve dropdown navigation test coverage. (#2066)
37+
3638
* `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)
3739

3840
* 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)

shiny/playwright/controller/_navs.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,35 @@ def click(self, *, timeout: Timeout = None) -> None:
145145
"""
146146
Clicks the nav panel.
147147
148+
If the nav panel is inside a dropdown, playwright will first open the dropdown before selecting the nav panel.
149+
148150
Parameters
149151
----------
150152
timeout
151153
The maximum time to wait for the nav panel to be visible and interactable. Defaults to `None`.
152154
"""
155+
156+
parent_ul_loc = self.loc.locator("..").locator("..")
157+
158+
parent_ul_cls = parent_ul_loc.element_handle().get_attribute("class")
159+
cls_menu_regex = re.compile(rf"(^|\s+){re.escape('dropdown-menu')}(\s+|$)")
160+
cls_show_regex = re.compile(rf"(^|\s+){re.escape('show')}(\s+|$)")
161+
cls_dropdown_regex = re.compile(rf"(^|\s+){re.escape('dropdown')}(\s+|$)")
162+
163+
# If the item is in a dropdown and the dropdown is closed
164+
if (
165+
parent_ul_cls
166+
and cls_menu_regex.search(parent_ul_cls)
167+
and not cls_show_regex.search(parent_ul_cls)
168+
):
169+
grandparent_li_loc = parent_ul_loc.locator("..")
170+
gnd_li_cls = grandparent_li_loc.element_handle().get_attribute("class")
171+
172+
# Confirm it is a dropdown
173+
if gnd_li_cls and cls_dropdown_regex.search(gnd_li_cls):
174+
# click the grandparent list item to open it before clicking the target item
175+
grandparent_li_loc.click()
176+
153177
self.loc.click(timeout=timeout)
154178

155179
def expect_active(self, value: bool, *, timeout: Timeout = None) -> None:
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from shiny.express import input, render, ui
2+
3+
with ui.navset_pill(id="selected_navset_pill"):
4+
with ui.nav_panel("A"):
5+
"Panel A content"
6+
7+
with ui.nav_panel("B"):
8+
"Panel B content"
9+
10+
with ui.nav_panel("C"):
11+
"Panel C content"
12+
13+
with ui.nav_menu("Other links"):
14+
with ui.nav_panel("D"):
15+
"Page D content"
16+
17+
"----"
18+
"Description:"
19+
with ui.nav_control():
20+
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
21+
ui.h5("Selected:")
22+
23+
24+
@render.code
25+
def _():
26+
return input.selected_navset_pill()
27+
28+
29+
with ui.navset_underline(id="selected_navset_underline"):
30+
with ui.nav_panel("A"):
31+
"Panel A content"
32+
33+
with ui.nav_panel("B"):
34+
"Panel B content"
35+
36+
with ui.nav_panel("C"):
37+
"Panel C content"
38+
39+
with ui.nav_menu("Other links"):
40+
with ui.nav_panel("D"):
41+
"Page D content"
42+
43+
"----"
44+
"Description:"
45+
with ui.nav_control():
46+
ui.a("Shiny", href="https://shiny.posit.co", target="_blank")
47+
ui.h5("Selected:")
48+
49+
50+
@render.code
51+
def _underline():
52+
return input.selected_navset_underline()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from typing import Union
2+
3+
import pytest
4+
from playwright.sync_api import Page
5+
6+
from shiny.playwright import controller
7+
from shiny.pytest import create_app_fixture
8+
from shiny.run import ShinyAppProc
9+
10+
app = create_app_fixture(["app-express.py"])
11+
12+
13+
@pytest.mark.parametrize(
14+
"nav_factory,nav_id,out_id",
15+
[
16+
(controller.NavsetPill, "selected_navset_pill", "_"),
17+
(controller.NavsetUnderline, "selected_navset_underline", "_underline"),
18+
],
19+
ids=["pill", "underline"],
20+
)
21+
def test_navset_menu(
22+
page: Page,
23+
app: ShinyAppProc,
24+
nav_factory: Union[type[controller.NavsetPill], type[controller.NavsetUnderline]],
25+
nav_id: str,
26+
out_id: str,
27+
):
28+
29+
page.goto(app.url)
30+
navset = nav_factory(page, nav_id)
31+
output: controller.OutputText = controller.OutputText(page, out_id)
32+
33+
navset.expect_value("A")
34+
output.expect_value("A")
35+
36+
for panel in ["B", "C", "D"]:
37+
navset.set(panel)
38+
navset.expect_value(panel)
39+
output.expect_value(panel)

0 commit comments

Comments
 (0)