Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
131 changes: 60 additions & 71 deletions shiny/playwright/controller/_input_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
from ..expect._internal import expect_style_to_have_value as _expect_style_to_have_value
from ._base import (
InitLocator,
UiWithContainerP,
UiWithLabel,
WidthContainerM,
WidthLocM,
_expect_multiple,
all_missing,
not_is_missing,
)
Expand Down Expand Up @@ -923,30 +923,40 @@ def __init__(
)


class _InputSelectBase(
WidthLocM,
UiWithLabel,
):
loc_selected: Locator
"""
Playwright `Locator` for the selected option of the input select.
"""
loc_choices: Locator
"""
Playwright `Locator` for the choices of the input select.
"""
loc_choice_groups: Locator
class InputSelectWidthM:
"""
Playwright `Locator` for the choice groups of the input select.
A base class representing the input `select` and `selectize` widths.

This class provides methods to expect the width attribute of a DOM element.
"""

def __init__(
self,
page: Page,
id: str,
def expect_width(
self: UiWithContainerP,
value: AttrValue,
*,
select_class: str = "",
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):
"""
Controller for :func:`shiny.ui.input_select`.

If you have defined your app's select input (`ui.input_select()`) with `selectize=TRUE`, use `InputSelectize` to test your app's UI.
"""

def __init__(self, page: Page, id: str) -> None:
"""
Initializes the input select.

Expand All @@ -956,13 +966,11 @@ def __init__(
The page where the input select is located.
id
The id of the input select.
select_class
The class of the select element. Defaults to "".
"""
super().__init__(
page,
id=id,
loc=f"select#{id}.shiny-bound-input{select_class}",
loc=f"select#{id}.shiny-bound-input.form-select",
)
self.loc_selected = self.loc.locator("option:checked")
self.loc_choices = self.loc.locator("option")
Expand All @@ -988,9 +996,29 @@ def set(
selected = [selected]
self.loc.select_option(value=selected, timeout=timeout)

# If `selectize=` parameter does not become deprecated, uncomment this
# # selectize: bool = False,
# def expect_selectize(self, value: bool, *, timeout: Timeout = None) -> None:
# """
# Expect the input select to be selectize.

# Parameters
# ----------
# value
# Whether the input select is selectize.
# timeout
# The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
# """
# # class_=None if selectize else "form-select",
# _expect_class_to_have_value(
# self.loc,
# "form-select",
# has_class=not value,
# timeout=timeout,
# )

def expect_choices(
self,
# TODO-future; support patterns?
choices: ListPatternOrStr,
*,
timeout: Timeout = None,
Expand Down Expand Up @@ -1111,10 +1139,9 @@ def expect_choice_labels(
return
playwright_expect(self.loc_choices).to_have_text(value, timeout=timeout)

# multiple: bool = False,
def expect_multiple(self, value: bool, *, timeout: Timeout = None) -> None:
"""
Expect the input select to allow multiple selections.
Expect the input selectize to allow multiple selections.

Parameters
----------
Expand All @@ -1123,7 +1150,12 @@ def expect_multiple(self, value: bool, *, timeout: Timeout = None) -> None:
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_multiple(self.loc, value, timeout=timeout)
_expect_attribute_to_have_value(
self.loc,
"multiple",
value="" if value else None,
timeout=timeout,
)

def expect_size(self, value: AttrValue, *, timeout: Timeout = None) -> None:
"""
Expand All @@ -1144,50 +1176,7 @@ def expect_size(self, value: AttrValue, *, timeout: Timeout = None) -> None:
)


class InputSelect(_InputSelectBase):
"""Controller for :func:`shiny.ui.input_select`."""

def __init__(self, page: Page, id: str) -> None:
"""
Initializes the input select.

Parameters
----------
page
The page where the input select is located.
id
The id of the input select.
"""
super().__init__(
page,
id=id,
select_class=".form-select",
)

# selectize: bool = False,
def expect_selectize(self, value: bool, *, timeout: Timeout = None) -> None:
"""
Expect the input select to be selectize.

Parameters
----------
value
Whether the input select is selectize.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
# class_=None if selectize else "form-select",
_expect_class_to_have_value(
self.loc,
"form-select",
has_class=not value,
timeout=timeout,
)


class InputSelectize(
UiWithLabel,
):
class InputSelectize(InputSelectWidthM, 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,83 @@
from shiny.express import input, render, ui

ui.page_opts(title="Select Inputs Kitchensink")

fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
fruits_dict = {
"apple": "Apple",
"banana": "Banana",
"cherry": "Cherry",
"date": "Date",
"elderberry": "Elderberry",
}
fruits_grouped_dict = {
"Citrus": {
"Orange": "Sweet and tangy",
"Lemon": "Zesty and refreshing",
"Lime": "Bright and tart",
},
"Berries": {
"Strawberry": "Juicy and sweet",
"Blueberry": "Tiny and antioxidant-rich",
"Raspberry": "Delicate and slightly tart",
},
}


with ui.card():
ui.input_select("basic_select", "Default select", fruits)

@render.code
def basic_result_txt():
return input.basic_select()


with ui.card():
ui.input_select("multi_select", "Multiple Select", fruits, multiple=True)

@render.code
def multi_result_txt():
return ", ".join(input.multi_select())


with ui.card():
ui.input_select(
"select_with_selected", "Select with selected", fruits, selected="Cherry"
)

@render.code
def select_with_selected_txt():
return str(input.select_with_selected())


with ui.card():
ui.input_select("width_select", "Select with Custom Width", fruits, width="400px")

@render.code
def width_result_txt():
return str(input.width_select())


with ui.card():
ui.input_select(
"select_with_labels",
"Select with labels",
fruits_dict,
)

@render.code
def select_with_labels_txt():
return str(input.select_with_labels())


with ui.card():
ui.input_select(
"select_with_custom_size_and_dict",
"Select with custom size and dict",
fruits_grouped_dict,
size="4",
)

@render.code
def select_with_custom_size_and_dict_txt():
return str(input.select_with_custom_size_and_dict())
Loading
Loading