diff --git a/CHANGELOG.md b/CHANGELOG.md index 07bd8939e..c9f165e31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `.expect_layout()` for Navset controllers in `shiny.playwright.controllers` is now renamed to `.expect_fluid()` and requires a `bool` value. To keep behavior the same, use `.expect_fluid(True)` (#1668) +* `.expect_icon()` for Accordion controllers in `shiny.playwright.controllers` now requires a `bool` value instead of a `str`. (#1710) + ### New features * Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.data_frame`. This allows for any eager data frame supported by narwhals to be returned from a `@render.data_frame` output method. All internal methods and helper methods leverage the `narwhals` API to be data frame agnostic. (#1570) @@ -31,6 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Small improvements to the default pulse busy indicator to better blend with any background. It's also now slightly smaller by default.(#1707) +* Added `.expect_class()` and `.expect_multiple()` for `Accordion` in `shiny.playwright.controllers` (#1710) + * Added [narwhals](https://posit-dev.github.io/py-narwhals) support for `@render.table`. This allows for any eager data frame supported by narwhals to be returned from a `@render.table` output method. (#1570) ### Bug fixes diff --git a/shiny/playwright/controller/_accordion.py b/shiny/playwright/controller/_accordion.py index 60e7de370..1e541d479 100644 --- a/shiny/playwright/controller/_accordion.py +++ b/shiny/playwright/controller/_accordion.py @@ -98,18 +98,22 @@ def expect_body(self, value: PatternOrStr, *, timeout: Timeout = None) -> None: """ playwright_expect(self.loc_body).to_have_text(value, timeout=timeout) - def expect_icon(self, value: PatternOrStr, *, timeout: Timeout = None) -> None: + def expect_icon(self, exists: bool, *, timeout: Timeout = None) -> None: """ - Expects the accordion panel icon to have the specified text. + Expects the accordion panel icon to exist or not. Parameters ---------- - value - The expected text pattern or string. + exists + `True` if the icon is expected to exist, `False` otherwise. timeout The maximum time to wait for the icon to appear. Defaults to `None`. """ - playwright_expect(self.loc_icon).to_have_text(value, timeout=timeout) + icon_child_loc = self.loc_icon.locator("> *") + if exists: + playwright_expect(icon_child_loc).not_to_have_count(0, timeout=timeout) + else: + playwright_expect(icon_child_loc).to_have_count(0, timeout=timeout) def expect_open(self, value: bool, *, timeout: Timeout = None) -> None: """ @@ -313,6 +317,52 @@ def set( ) self.accordion_panel(elem_value).set(elem_value in open, timeout=timeout) + def expect_class( + self, + class_name: str, + *, + timeout: Timeout = None, + ) -> None: + """ + Expects the accordion to have the specified class. + + Parameters + ---------- + class_name + The class name to expect. + timeout + The maximum time to wait for the class to appear. Defaults to `None`. + """ + _expect_class_to_have_value( + self.loc_container, + class_name, + has_class=True, + timeout=timeout, + ) + + def expect_multiple( + self, + value: bool, + *, + timeout: Timeout = None, + ) -> None: + """ + Expects the accordion to be multiple or not. + + Parameters + ---------- + value + `True` if the accordion is expected to be multiple, `False` otherwise. + timeout + The maximum time to wait for the expectation to pass. Defaults to `None`. + """ + _expect_class_to_have_value( + self.loc_container, + "autoclose", + has_class=not value, + timeout=timeout, + ) + def accordion_panel( self, data_value: str, diff --git a/tests/playwright/shiny/components/accordion/test_accordion.py b/tests/playwright/shiny/components/accordion/test_accordion.py index adb038d03..174e18d7a 100644 --- a/tests/playwright/shiny/components/accordion/test_accordion.py +++ b/tests/playwright/shiny/components/accordion/test_accordion.py @@ -66,7 +66,7 @@ def test_accordion(page: Page, local_app: ShinyAppProc) -> None: toggle_updates_button.click() acc_panel_updated_A.expect_label("Updated title") acc_panel_updated_A.expect_body("Updated body") - acc_panel_updated_A.expect_icon("Look! An icon! -->") + acc_panel_updated_A.expect_icon(True) acc.expect_panels(["updated_section_a", "Section B", "Section C", "Section D"]) # workaround - toggle it twice Section A diff --git a/tests/playwright/shiny/inputs/accordion_kitchensink/app.py b/tests/playwright/shiny/inputs/accordion_kitchensink/app.py new file mode 100644 index 000000000..b3305509b --- /dev/null +++ b/tests/playwright/shiny/inputs/accordion_kitchensink/app.py @@ -0,0 +1,46 @@ +from faicons import icon_svg + +from shiny import App, Inputs, ui + +app_ui = ui.page_fluid( + ui.h1("Accordion Kitchensink"), + ui.accordion( + ui.accordion_panel( + "Panel 1", + ui.p("This is the content of Panel 1"), + value="panel1", + ), + ui.accordion_panel( + "Panel 2", + ui.p("This is the content of Panel 2"), + icon=icon_svg("trash-arrow-up"), + value="panel2", + ), + id="accordion_1", + width="600px", + height="300px", + multiple=False, + class_="bg-light", + ), + ui.accordion( + ui.accordion_panel( + "Panel 3", + ui.p("This is the content of Panel 3"), + value="panel3", + ), + ui.accordion_panel( + "Panel 4", + ui.p("This is the content of Panel 4"), + value="panel4", + ), + id="accordion_2", + multiple=True, + ), +) + + +def server(input: Inputs): + pass + + +app = App(app_ui, server) diff --git a/tests/playwright/shiny/inputs/accordion_kitchensink/test_accordion_kitchensink.py b/tests/playwright/shiny/inputs/accordion_kitchensink/test_accordion_kitchensink.py new file mode 100644 index 000000000..9cbe5a8f4 --- /dev/null +++ b/tests/playwright/shiny/inputs/accordion_kitchensink/test_accordion_kitchensink.py @@ -0,0 +1,40 @@ +from playwright.sync_api import Page + +from shiny.playwright import controller +from shiny.run import ShinyAppProc + + +def test_accordion_kitchensink(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + accordion1 = controller.Accordion(page, "accordion_1") + accordion1.expect_width("600px") + accordion1.expect_height("300px") + accordion1.expect_class("bg-light") + accordion1.expect_multiple(False) + accordion1_panel1 = accordion1.accordion_panel("panel1") + accordion1_panel1.expect_open(True) + accordion1_panel1.expect_label("Panel 1") + accordion1_panel1.expect_icon(False) + + accordion1_panel2 = accordion1.accordion_panel("panel2") + accordion1_panel2.expect_open(False) + accordion1_panel2.expect_label("Panel 2") + accordion1_panel2.expect_icon(True) + accordion1_panel2.set(True) + accordion1_panel2.expect_open(True) + accordion1_panel1.expect_open(False) + + accordion2 = controller.Accordion(page, "accordion_2") + accordion2.expect_width(None) + accordion2.expect_height(None) + accordion2.expect_multiple(True) + + accordion2_panel3 = accordion2.accordion_panel("panel3") + accordion2_panel3.expect_open(True) + + accordion2_panel4 = accordion2.accordion_panel("panel4") + accordion2_panel4.expect_open(False) + accordion2_panel4.set(True) + accordion2_panel4.expect_open(True) + accordion2_panel3.expect_open(True)