Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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: 1 addition & 1 deletion docs/_quartodoc-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ quartodoc:
- title: Navigation (tab) panels
desc: Methods for interacting with Shiny app UI content controller.
contents:
- playwright.controller.NavPanel
- playwright.controller.NavsetBar
- playwright.controller.NavsetCardPill
- playwright.controller.NavsetCardTab
Expand All @@ -58,6 +57,7 @@ quartodoc:
- playwright.controller.NavsetPillList
- playwright.controller.NavsetTab
- playwright.controller.NavsetUnderline
- playwright.controller.NavPanel
- title: Upload and download
desc: Methods for interacting with Shiny app uploading and downloading controller.
contents:
Expand Down
230 changes: 213 additions & 17 deletions shiny/playwright/controller/_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -1587,7 +1587,6 @@ def set(self, value: bool, *, timeout: Timeout = None, **kwargs: object) -> None
value, timeout=timeout, **kwargs # pyright: ignore[reportArgumentType]
)

# TODO-karan-test: Convert usage of _toggle() to set()
def _toggle(self, *, timeout: Timeout = None, **kwargs: object) -> None:
"""
Toggles the input checkbox.
Expand Down Expand Up @@ -5441,13 +5440,22 @@ def _toggle(self, timeout: Timeout = None) -> None:
self.loc_trigger.hover(timeout=timeout)


class _NavPanelBase(_UiWithContainer):
class _NavSetBase(_UiWithContainer):
"""A Base mixin class for Nav controls"""

def nav_panel(
self,
value: str,
) -> NavPanel:
"""
Returns the nav panel (:class:`~shiny.playwright.controls.NavPanel`)
with the specified value.

Parameters
----------
value
The value of the nav panel.
"""
return NavPanel(self.page, self.id, value)

def set(self, value: str, *, timeout: Timeout = None) -> None:
Expand Down Expand Up @@ -5500,6 +5508,21 @@ def get_loc_active_content(self, *, timeout: Timeout = None) -> Locator:
f"div.tab-content[data-tabsetid='{datatab_id}'] > div.tab-pane.active"
)

def expect_title(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
"""
Expects the nav panel to have the specified title.

Parameters
----------
value
The expected title.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
playwright_expect(self.page.locator(f"span:has(+ ul#{self.id})")).to_have_text(
value, timeout=timeout
)

def _expect_content_text(
self, value: PatternOrStr, *, timeout: Timeout = None
) -> None:
Expand Down Expand Up @@ -5577,7 +5600,7 @@ class NavPanel(_UiWithContainer):
Playwright `Locator` for the nav panel container.
"""

def __init__(self, page: Page, id: str, data_value: str) -> None:
def __init__(self, page: Page, id: str, panel_value: str) -> None:
"""
Initializes a new instance of the `NavPanel` class.

Expand All @@ -5587,17 +5610,17 @@ def __init__(self, page: Page, id: str, data_value: str) -> None:
Playwright `Page` of the Shiny app.
id
The ID of the nav panel.
data_value
The data value of the nav panel.
panel_value
The panel value of the nav panel.
"""
super().__init__(
page,
id=id,
loc=f"a[role='tab'][data-value='{data_value}']",
loc=f"a[role='tab'][data-value='{panel_value}']",
loc_container=f"ul#{id}",
)

self._data_value: str = data_value
self.panel_value: str = panel_value

# TODO-future: Make it a single locator expectation
# get active content instead of assertion
Expand All @@ -5610,7 +5633,7 @@ def loc_content(self) -> Locator:
"""
datatab_id = self.loc_container.get_attribute("data-tabsetid")
return self.page.locator(
f"div.tab-content[data-tabsetid='{datatab_id}'] > div.tab-pane[data-value='{self._data_value}']"
f"div.tab-content[data-tabsetid='{datatab_id}'] > div.tab-pane[data-value='{self.panel_value}']"
)

def click(self, *, timeout: Timeout = None) -> None:
Expand Down Expand Up @@ -5658,7 +5681,7 @@ def _expect_content_text(
playwright_expect(self.loc_content).to_have_text(value, timeout=timeout)


class NavsetTab(_NavPanelBase):
class NavsetTab(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_tab`."""

loc: Locator
Expand Down Expand Up @@ -5689,7 +5712,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetPill(_NavPanelBase):
class NavsetPill(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_pill`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5711,7 +5734,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetUnderline(_NavPanelBase):
class NavsetUnderline(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_underline`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5733,7 +5756,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetPillList(_NavPanelBase):
class NavsetPillList(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_pill_list`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5754,8 +5777,26 @@ def __init__(self, page: Page, id: str) -> None:
loc="> li.nav-item",
)

def expect_well(self, has_well: bool, *, timeout: Timeout = None) -> None:
"""
Expects the navset pill list to have a well.

class NavsetCardTab(_NavPanelBase):
Parameters
----------
has_well
`True` if the navset pill list is expected to have a well, `False` otherwise.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
if has_well:
playwright_expect(self.loc_container.locator("..")).to_have_class("well")
else:
playwright_expect(self.loc_container.locator("..")).not_to_have_class(
"well"
)


class NavsetCardTab(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_card_tab`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5777,7 +5818,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetCardPill(_NavPanelBase):
class NavsetCardPill(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_card_pill`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5799,7 +5840,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetCardUnderline(_NavPanelBase):
class NavsetCardUnderline(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_card_underline`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5820,8 +5861,45 @@ def __init__(self, page: Page, id: str) -> None:
loc="> li.nav-item",
)

def expect_placement(
self, location: Literal["above", "below"] = "above", *, timeout: Timeout = None
) -> None:
"""
Expects the navset card underline to have the specified placement.

class NavsetHidden(_NavPanelBase):
Parameters
----------
location
The expected placement location. Defaults to `'above'`.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
if location == "below":
playwright_expect(self.loc_container.locator("..")).to_have_class(
"card-footer", timeout=timeout
)
else:
playwright_expect(self.loc_container.locator("..")).to_have_class(
"card-header", timeout=timeout
)

def expect_sidebar(self, sidebar: bool, *, timeout: Timeout = None) -> None:
"""
Expects the navset card underline to have the specified sidebar.

Parameters
----------
sidebar
`True` if the sidebar is expected to be visible, `False` otherwise.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
playwright_expect(
self.loc_container.locator("..").locator("+ .bslib-sidebar-layout")
).to_have_count(int(sidebar), timeout=timeout)


class NavsetHidden(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_hidden`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5843,7 +5921,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class NavsetBar(_NavPanelBase):
class NavsetBar(_NavSetBase):
"""Controller for :func:`shiny.ui.navset_bar`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -5864,6 +5942,124 @@ def __init__(self, page: Page, id: str) -> None:
loc="> li.nav-item",
)

def _loc_navbar(self) -> Locator:
"""
Returns the locator for the navbar.
"""
return self.loc_container.locator("..").locator("..").locator("..")

def expect_position(
self,
position: Literal[
"fixed-top", "fixed-bottom", "static-top", "sticky-top"
] = "static-top",
*,
timeout: Timeout = None,
) -> None:
"""
Expects the navset bar to have the specified position.

Parameters
----------
position
The expected position. Defaults to `'static-top'`.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
if position == "static-top":
playwright_expect(self._loc_navbar()).not_to_have_class(
re.compile(rf"{position}"), timeout=timeout
)
else:
playwright_expect(self._loc_navbar()).to_have_class(
re.compile(rf"{position}"), timeout=timeout
)

def expect_inverse(self, *, timeout: Timeout = None) -> None:
"""
Expects the navset bar to be light text color if inverse is True

Parameters
----------
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
playwright_expect(self._loc_navbar()).to_have_class(
re.compile("navbar-inverse"), timeout=timeout
)

def expect_bg(self, bg: str, *, timeout: Timeout = None) -> None:
"""
Expects the navset bar to have the specified background color.

Parameters
----------
bg
The expected background color.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
_expect_style_to_have_value(
self._loc_navbar(), "background-color", f"{bg} !important", timeout=timeout
)

def expect_sidebar(self, sidebar: bool, *, timeout: Timeout = None) -> None:
"""
Expects the navset bar to have the specified sidebar.

Parameters
----------
sidebar
`True` if the sidebar is expected to be visible, `False` otherwise.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
if sidebar:
playwright_expect(
self.get_loc_active_content().locator("..").locator("..").locator("..")
).to_have_class(re.compile("bslib-sidebar-layout"), timeout=timeout)
else:
playwright_expect(
self.get_loc_active_content().locator("..").locator("..").locator("..")
).not_to_have_class(re.compile("bslib-sidebar-layout"), timeout=timeout)

def expect_gap(self, gap: str, *, timeout: Timeout = None) -> None:
"""
Expects the navset bar to have the specified gap.

Parameters
----------
gap
The expected gap.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
_expect_style_to_have_value(
self.get_loc_active_content(), "gap", gap, timeout=timeout
)

def expect_layout(
self, layout: Literal["fluid", "fixed"] = "fluid", *, timeout: Timeout = None
) -> None:
"""
Expects the navset bar to have the specified layout.

Parameters
----------
layout
The expected layout.
timeout
The maximum time to wait for the expectation to pass. Defaults to `None`.
"""
if layout == "fluid":
playwright_expect(
self.loc_container.locator("..").locator("..")
).to_have_class(re.compile("container-fluid"), timeout=timeout)
else:
playwright_expect(self.loc_container.locator("..")).to_have_class(
re.compile("container"), timeout=timeout
)


class Chat(_UiBase):
"""Controller for :func:`shiny.ui.chat`."""
Expand Down
Loading
Loading