Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
49 changes: 44 additions & 5 deletions shiny/playwright/controller/_input_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -923,8 +923,35 @@ def __init__(
)


class InputSelectionWidthM:
"""
A mixin class representing the input `select` and `selectize` widths.

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

loc_label: Locator
"""
Playwright `Locator` for the label of the UI element.
"""

def expect_width(self, 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_label.locator(".."), "width", value, timeout=timeout
)


class _InputSelectBase(
WidthLocM,
UiWithLabel,
):
loc_selected: Locator
Expand Down Expand Up @@ -1144,7 +1171,7 @@ def expect_size(self, value: AttrValue, *, timeout: Timeout = None) -> None:
)


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

def __init__(self, page: Page, id: str) -> None:
Expand Down Expand Up @@ -1185,9 +1212,7 @@ def expect_selectize(self, value: bool, *, timeout: Timeout = None) -> None:
)


class InputSelectize(
UiWithLabel,
):
class InputSelectize(UiWithLabel, InputSelectionWidthM):
"""Controller for :func:`shiny.ui.input_selectize`."""

def __init__(self, page: Page, id: str) -> None:
Expand All @@ -1205,6 +1230,9 @@ def __init__(self, page: Page, id: str) -> None:
# We are only guaranteed to have `data-value` attribute for each _option_
self.loc_choices = self._loc_selectize.locator("[data-value]")
self.loc_selected = self.loc_container.locator(f"select#{id} > option")
self.clear = self.loc.locator("..").locator(
"> div.plugin-clear_button > a.clear"
)

def set(
self,
Expand Down Expand Up @@ -1375,6 +1403,17 @@ def expect_multiple(self, value: bool, *, timeout: Timeout = None) -> None:
else:
_expect_attribute_to_have_value(self.loc, "multiple", None, timeout=timeout)

def clear_selection(self, *, timeout: Timeout = None) -> None:
"""
Clear the current selection of the input selectize.

Parameters
----------
timeout
The maximum time to wait for the selection to be cleared. Defaults to `None`.
"""
self.clear.click(timeout=timeout)


class InputSlider(_InputSliderBase):
"""Controller for :func:`shiny.ui.input_slider`."""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from shiny.express import input, render, ui

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

fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
fruits_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)

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

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

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

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


@render.text
def basic_result_txt():
return f"Basic select: {input.basic_select()}"


@render.text
def multi_result_txt():
return f"Multi select: {', '.join(input.multi_select())}"


@render.text
def select_with_selected_txt():
return f"Select with selected: {input.select_with_selected()}"


@render.text
def width_result_txt():
return f"Width select: {input.width_select()}"


@render.text
def select_with_custom_size_and_dict_txt():
return f"Dict and custom select: {input.select_with_custom_size_and_dict()}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from playwright.sync_api import Page

from shiny.playwright import controller
from shiny.run import ShinyAppProc


def test_input_select_kitchensink(page: Page, local_app: ShinyAppProc) -> None:
page.goto(local_app.url)

basic_select = controller.InputSelect(page, "basic_select")
basic_select_txt = controller.OutputText(page, "basic_result_txt")
basic_select.expect_label("Default select")
basic_select_txt.expect_value("Basic select: Apple")

multiple_select = controller.InputSelect(page, "multi_select")
multiple_select_txt = controller.OutputText(page, "multi_result_txt")
multiple_select.set(["Banana", "Cherry"])
# multiple_select.expect_multiple(True) # TODO-karan: Investigate why this is failing
multiple_select_txt.expect_value("Multi select: Banana, Cherry")

select_with_selected = controller.InputSelect(page, "select_with_selected")
select_with_selected_txt = controller.OutputText(page, "select_with_selected_txt")
select_with_selected.expect_selected("Cherry")
select_with_selected_txt.expect_value("Select with selected: Cherry")

select_with_width = controller.InputSelect(page, "width_select")
select_with_width.expect_width("400px")

select_with_custom_size_and_dict = controller.InputSelect(
page, "select_with_custom_size_and_dict"
)
select_with_custom_size_and_dict.expect_choice_groups(["Citrus", "Berries"])
select_with_custom_size_and_dict.expect_size("4")
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from shiny.express import input, render, ui

ui.page_opts(title="Selectize Inputs kitchensink")

fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
fruits_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",
},
}


ui.input_selectize("basic_selectize", "Default selectize", fruits)


ui.input_selectize("multi_selectize", "Multiple Selectize", fruits, multiple=True)


ui.input_selectize(
"selectize_with_selected", "Selectize with selected", fruits, selected="Cherry"
)


ui.input_selectize(
"selectize_width_close_button",
"Selectize with Custom Width and remove btn",
fruits_dict,
width="400px",
remove_button=True,
)


@render.text
def basic_result_txt():
return f"Basic select: {input.basic_selectize()}"


@render.text
def multi_result_txt():
return f"Multi select: {', '.join(input.multi_selectize())}"


@render.text
def selected_result_txt():
return f"Select with selected: {input.selectize_with_selected()}"


@render.text
def selectize_width_close_button_txt():
return f"Selectize with close button: {input.selectize_width_close_button()}"
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from playwright.sync_api import Page

from shiny.playwright import controller
from shiny.run import ShinyAppProc


def test_input_selectize_kitchensink(page: Page, local_app: ShinyAppProc) -> None:
page.goto(local_app.url)

basic_selectize = controller.InputSelectize(page, "basic_selectize")
basic_select_txt = controller.OutputText(page, "basic_result_txt")
basic_selectize.expect_label("Default selectize")
basic_select_txt.expect_value("Basic select: Apple")

multiple_selectize = controller.InputSelectize(page, "multi_selectize")
multiple_selectize_txt = controller.OutputText(page, "multi_result_txt")
multiple_selectize.set(["Banana", "Cherry"])
multiple_selectize_txt.expect_value("Multi select: Banana, Cherry")

selectize_with_selected = controller.InputSelectize(page, "selectize_with_selected")
selectize_with_selected_txt = controller.OutputText(page, "selected_result_txt")
selectize_with_selected.expect_selected(["Cherry"])
selectize_with_selected_txt.expect_value("Select with selected: Cherry")

selectize_width_close_button = controller.InputSelectize(
page, "selectize_width_close_button"
)
selectize_width_close_button_txt = controller.OutputText(
page, "selectize_width_close_button_txt"
)
selectize_width_close_button_txt.expect_value("Selectize with close button: Orange")
selectize_width_close_button.expect_width("400px")
selectize_width_close_button.expect_choice_groups(["Citrus", "Berries"])
selectize_width_close_button.clear_selection()
selectize_width_close_button_txt.expect_value(
"Selectize with close button: "
) # Expecting empty after clear
Loading