diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e29a5bd3..b330666b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Fixed the `InputDate` and `InputDateRange` playwright controllers to check the `width` property within the `style` attribute. (#1696) +* Fixed the `InputCheckbox` and `InputCheckboxGroup` playwright controllers' `.expect_width()` to check the `width` property within the `style` attribute. (#1702) + ## [1.1.0] - 2024-09-03 ### New features diff --git a/shiny/playwright/controller/_base.py b/shiny/playwright/controller/_base.py index 119e99dab..329a47324 100644 --- a/shiny/playwright/controller/_base.py +++ b/shiny/playwright/controller/_base.py @@ -387,6 +387,30 @@ def expect_width( ) +class WidthContainerStyleM: + """ + A mixin class that provides methods to control the width of input elements, such as checkboxes, sliders and radio buttons. + """ + + def expect_width( + self: UiWithContainerP, + value: AttrValue, + *, + timeout: Timeout = None, + ) -> None: + """ + Expect the input element to have a specific width. + + Parameters + ---------- + value + The expected width. + timeout + The maximum time to wait for the expectation to be fulfilled. Defaults to `None`. + """ + _expect_style_to_have_value(self.loc_container, "width", value, timeout=timeout) + + class InputActionBase(UiBase): def expect_label( self, diff --git a/shiny/playwright/controller/_input_controls.py b/shiny/playwright/controller/_input_controls.py index 54fc5ac12..98235831a 100644 --- a/shiny/playwright/controller/_input_controls.py +++ b/shiny/playwright/controller/_input_controls.py @@ -17,9 +17,8 @@ from ..expect._internal import expect_style_to_have_value as _expect_style_to_have_value from ._base import ( InitLocator, - UiWithContainerP, UiWithLabel, - WidthContainerM, + WidthContainerStyleM, all_missing, not_is_missing, ) @@ -29,7 +28,10 @@ ) -class _InputSliderBase(UiWithLabel): +class _InputSliderBase( + WidthContainerStyleM, + UiWithLabel, +): loc_irs: Locator """ @@ -202,19 +204,6 @@ def expect_max(self, value: AttrValue, *, timeout: Timeout = None) -> None: self.loc, "data-max", value=value, timeout=timeout ) - def expect_width(self, value: str, *, timeout: Timeout = None) -> None: - """ - Expects the slider to have the specified width. - - Parameters - ---------- - value - The expected width. - timeout - The maximum time to wait for the expectation to be fulfilled. Defaults to `None`. - """ - _expect_style_to_have_value(self.loc_container, "width", value, timeout=timeout) - def expect_step(self, value: AttrValue, *, timeout: Timeout = None) -> None: """ Expect the input element to have the expected `step` attribute value. @@ -463,7 +452,10 @@ def _handle_center( return handle_center -class _RadioButtonCheckboxGroupBase(UiWithLabel): +class _RadioButtonCheckboxGroupBase( + WidthContainerStyleM, + UiWithLabel, +): loc_choice_labels: Locator def expect_choice_labels( @@ -511,7 +503,6 @@ def expect_inline(self, value: bool, *, timeout: Timeout = None) -> None: class InputRadioButtons( - WidthContainerM, _RadioButtonCheckboxGroupBase, ): """Controller for :func:`shiny.ui.input_radio_buttons`.""" @@ -646,7 +637,7 @@ def expect_selected( class _InputCheckboxBase( - WidthContainerM, + WidthContainerStyleM, UiWithLabel, ): def __init__( @@ -721,7 +712,6 @@ def expect_checked(self, value: bool, *, timeout: Timeout = None) -> None: class InputCheckboxGroup( - WidthContainerM, _RadioButtonCheckboxGroupBase, ): """Controller for :func:`shiny.ui.input_checkbox_group`.""" @@ -935,33 +925,10 @@ def __init__( ) -class InputSelectWidthM: - """ - A base class representing the input `select` and `selectize` widths. - - This class provides methods to expect the width attribute of a DOM element. - """ - - def expect_width( - self: UiWithContainerP, - value: AttrValue, - *, - timeout: Timeout = None, - ) -> None: - """ - Expect the input select to have a specific width. - - Parameters - ---------- - value - The expected width. - timeout - The maximum time to wait for the expectation to be fulfilled. Defaults to `None`. - """ - _expect_style_to_have_value(self.loc_container, "width", value, timeout=timeout) - - -class InputSelect(InputSelectWidthM, UiWithLabel): +class InputSelect( + WidthContainerStyleM, + UiWithLabel, +): """ Controller for :func:`shiny.ui.input_select`. @@ -1188,7 +1155,10 @@ def expect_size(self, value: AttrValue, *, timeout: Timeout = None) -> None: ) -class InputSelectize(InputSelectWidthM, UiWithLabel): +class InputSelectize( + WidthContainerStyleM, + UiWithLabel, +): """Controller for :func:`shiny.ui.input_selectize`.""" def __init__(self, page: Page, id: str) -> None: diff --git a/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/app.py b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/app.py new file mode 100644 index 000000000..647fe4339 --- /dev/null +++ b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/app.py @@ -0,0 +1,28 @@ +from shiny.express import input, render, ui + +ui.page_opts(title="Checkbox Kitchen Sink", fillable=True) + +with ui.layout_columns(): + with ui.card(): + ui.card_header("Default checkbox with label") + ui.input_checkbox("default", "Basic Checkbox") + + @render.code + def default_txt(): + return str(input.default()) + + with ui.card(): + ui.card_header("Checkbox With Value") + ui.input_checkbox("value", "Checkbox with Value", value=True) + + @render.code + def value_txt(): + return str(input.value()) + + with ui.card(): + ui.card_header("Checkbox With Width") + ui.input_checkbox("width", "Checkbox with Width", width="10px") + + @render.code + def width_txt(): + return str(input.width()) diff --git a/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/test_input_checkbox_kitchensink.py b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/test_input_checkbox_kitchensink.py new file mode 100644 index 000000000..731b91650 --- /dev/null +++ b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox/test_input_checkbox_kitchensink.py @@ -0,0 +1,26 @@ +from playwright.sync_api import Page + +from shiny.playwright import controller +from shiny.run import ShinyAppProc + + +def test_checkbox_kitchen(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + default = controller.InputCheckbox(page, "default") + default.expect_label("Basic Checkbox") + default.expect_checked(False) + default_code = controller.OutputCode(page, "default_txt") + default_code.expect_value("False") + default.set(True) + default_code.expect_value("True") + default.set(False) + default_code.expect_value("False") + + value = controller.InputCheckbox(page, "value") + value.expect_checked(True) + controller.OutputCode(page, "value_txt").expect_value("True") + + width = controller.InputCheckbox(page, "width") + width.expect_width("10px") + controller.OutputCode(page, "width_txt").expect_value("False") diff --git a/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/app.py b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/app.py new file mode 100644 index 000000000..47a381c05 --- /dev/null +++ b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/app.py @@ -0,0 +1,64 @@ +from shiny.express import input, render, ui + +ui.page_opts(title="Checkbox Group Kitchen Sink", fillable=True) + +choices = ["Option A", "Option B", "Option C", "Option D"] + +choices_dict = { + "value1": "Option A", + "value2": "Option B", + "value3": "Option C", + "value4": "Option D", +} + +with ui.layout_columns(): + with ui.card(): + ui.card_header("Default Checkbox Group with label") + ui.input_checkbox_group("default", "Basic Checkbox Group", choices=choices) + + @render.code + def default_txt(): + return str(input.default()) + + with ui.card(): + ui.card_header("With Selected Values") + ui.input_checkbox_group( + "selected", + "Selected Values", + choices=choices, + selected=["Option B", "Option C"], + ) + + @render.code + def selected_txt(): + return str(input.selected()) + + with ui.card(): + ui.card_header("With Width") + ui.input_checkbox_group("width", "Custom Width", choices=choices, width="30px") + + @render.code + def width_txt(): + return str(input.width()) + + with ui.card(): + ui.card_header("Inline") + ui.input_checkbox_group( + "inline", "Inline Checkbox Group", choices=choices, inline=True + ) + + @render.code + def inline_txt(): + return str(input.inline()) + + with ui.card(): + ui.card_header("With dict of values") + ui.input_checkbox_group( + "dict_values", + "Dict Values", + choices=choices_dict, + ) + + @render.code + def dict_values_txt(): + return str(input.dict_values()) diff --git a/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/test_input_checkbox_group_kitchensink.py b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/test_input_checkbox_group_kitchensink.py new file mode 100644 index 000000000..6ee55d8be --- /dev/null +++ b/tests/playwright/shiny/inputs/input_controls_kitchensink/input_checkbox_group/test_input_checkbox_group_kitchensink.py @@ -0,0 +1,36 @@ +from playwright.sync_api import Page + +from shiny.playwright import controller +from shiny.run import ShinyAppProc + + +def test_checkbox_group_kitchen(page: Page, local_app: ShinyAppProc) -> None: + page.goto(local_app.url) + + default = controller.InputCheckboxGroup(page, "default") + default.expect_label("Basic Checkbox Group") + default.expect_selected([]) + controller.OutputCode(page, "default_txt").expect_value("()") + + selected = controller.InputCheckboxGroup(page, "selected") + selected.expect_selected(["Option B", "Option C"]) + controller.OutputCode(page, "selected_txt").expect_value("('Option B', 'Option C')") + + width = controller.InputCheckboxGroup(page, "width") + width.expect_width("30px") + + inline = controller.InputCheckboxGroup(page, "inline") + inline.expect_inline(True) + inline_txt = controller.OutputCode(page, "inline_txt") + inline_txt.expect_value("()") + # Set in wrong order + inline.set(["Option D", "Option A"]) + inline.expect_selected(["Option A", "Option D"]) + inline_txt.expect_value("('Option A', 'Option D')") + + dict_values = controller.InputCheckboxGroup(page, "dict_values") + dict_values.expect_selected([]) + dict_values.set(["value1", "value4"]) + dict_values.expect_selected(["value1", "value4"]) + dict_values_txt = controller.OutputCode(page, "dict_values_txt") + dict_values_txt.expect_value("('value1', 'value4')")