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 @@ -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
Expand Down
24 changes: 24 additions & 0 deletions shiny/playwright/controller/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
66 changes: 18 additions & 48 deletions shiny/playwright/controller/_input_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -29,7 +28,10 @@
)


class _InputSliderBase(UiWithLabel):
class _InputSliderBase(
WidthContainerStyleM,
UiWithLabel,
):

loc_irs: Locator
"""
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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`."""
Expand Down Expand Up @@ -646,7 +637,7 @@ def expect_selected(


class _InputCheckboxBase(
WidthContainerM,
WidthContainerStyleM,
UiWithLabel,
):
def __init__(
Expand Down Expand Up @@ -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`."""
Expand Down Expand Up @@ -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`.

Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -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')")
Loading