Skip to content
25 changes: 25 additions & 0 deletions shiny/playwright/controller/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,31 @@ def expect_label(
playwright_expect(self.loc_label).to_have_text(value, timeout=timeout)


class WidthLocStlyeM:
"""
A mixin class that provides methods to control the width of input action buttons and action links.

"""

def expect_width(
self: UiBaseP,
value: AttrValue,
*,
timeout: Timeout = None,
) -> None:
"""
Expect the `width` attribute of a DOM element to have a specific value.

Parameters
----------
value
The expected value of the `width` attribute.
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_style_to_have_value(self.loc, "width", value, timeout=timeout)


class WidthLocM:
"""
A mixin class representing the `.loc`'s width.
Expand Down
23 changes: 20 additions & 3 deletions shiny/playwright/controller/_input_buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@
expect_attribute_to_have_value as _expect_attribute_to_have_value,
)
from ..expect._internal import expect_style_to_have_value as _expect_style_to_have_value
from ._base import InputActionBase, UiBase, UiWithLabel, WidthLocM, _expect_multiple
from ._base import (
InputActionBase,
UiBase,
UiWithLabel,
WidthLocStlyeM,
_expect_multiple,
)


class InputActionButton(
WidthLocM,
WidthLocStlyeM,
InputActionBase,
):
"""Controller for :func:`shiny.ui.input_action_button`."""
Expand All @@ -43,6 +49,17 @@ def __init__(
loc=f"button#{id}.action-button.shiny-bound-input",
)

def expect_disabled(self, *, timeout: Timeout = None):
"""
Expect the input action button to be disabled.

Parameters
----------
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
playwright_expect(self.loc).to_have_attribute("disabled", "", timeout=timeout)


class InputDarkMode(UiBase):
"""Controller for :func:`shiny.ui.input_dark_mode`."""
Expand Down Expand Up @@ -131,7 +148,7 @@ def expect_attribute(self, value: str, *, timeout: Timeout = None):


class InputTaskButton(
WidthLocM,
WidthLocStlyeM,
InputActionBase,
):
"""Controller for :func:`shiny.ui.input_task_button`."""
Expand Down
42 changes: 8 additions & 34 deletions shiny/playwright/controller/_input_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,14 @@
from ._base import (
Resize,
UiBaseP,
UiWithContainerP,
UiWithLabel,
WidthLocM,
WidthContainerStyleM,
all_missing,
not_is_missing,
set_text,
)


class InputDateWidthM:
"""
A mixin class for input date width.
This mixin class provides methods to expect the width of input date elements.
"""

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 _SetTextM:
def set(self: UiBaseP, value: str, *, timeout: Timeout = None) -> None:
"""
Expand Down Expand Up @@ -91,7 +65,7 @@ def expect_value(
class InputNumeric(
_SetTextM,
_ExpectTextInputValueM,
WidthLocM,
WidthContainerStyleM,
UiWithLabel,
):
"""Controller for :func:`shiny.ui.input_numeric`."""
Expand Down Expand Up @@ -242,7 +216,7 @@ def expect_autocomplete(
class InputText(
_SetTextM,
_ExpectTextInputValueM,
WidthLocM,
WidthContainerStyleM,
_ExpectPlaceholderAttrM,
_ExpectAutocompleteAttrM,
_ExpectSpellcheckAttrM,
Expand Down Expand Up @@ -271,6 +245,7 @@ def __init__(self, page: Page, id: str) -> None:
class InputPassword(
_SetTextM,
_ExpectTextInputValueM,
WidthContainerStyleM,
_ExpectPlaceholderAttrM,
UiWithLabel,
):
Expand Down Expand Up @@ -316,6 +291,7 @@ def expect_width(

class InputTextArea(
_SetTextM,
WidthContainerStyleM,
_ExpectTextInputValueM,
_ExpectPlaceholderAttrM,
_ExpectAutocompleteAttrM,
Expand Down Expand Up @@ -421,9 +397,7 @@ def expect_resize(
timeout
The maximum time to wait for the expectation to be fulfilled. Defaults to `None`.
"""
_expect_attribute_to_have_value(
self.loc, "resize", value=value, timeout=timeout
)
_expect_style_to_have_value(self.loc, "resize", value=value, timeout=timeout)

def expect_autoresize(
self,
Expand All @@ -450,7 +424,7 @@ def expect_autoresize(


class _DateBase(
InputDateWidthM,
WidthContainerStyleM,
_SetTextM,
UiWithLabel,
):
Expand Down Expand Up @@ -694,7 +668,7 @@ def __init__(self, page: Page, id: str) -> None:
)


class InputDateRange(InputDateWidthM, UiWithLabel):
class InputDateRange(WidthContainerStyleM, UiWithLabel):
"""Controller for :func:`shiny.ui.input_date_range`."""

loc_separator: Locator
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from faicons import icon_svg

from shiny.express import input, render, ui

ui.page_opts(title="Kitchen Sink: ui.input_action_button()", fillable=True)

with ui.layout_columns():
with ui.card():
ui.h3("Default Action Button")
ui.input_action_button("default", label="Default button")

@render.code
def default_txt():
return f"Button clicked {input.default()} times"

with ui.card():
ui.h3("With Custom Width")
ui.input_action_button("width", "Wide button", width="200px")

@render.code
def width_txt():
return f"Button clicked {input.width()} times"

with ui.card():
ui.h3("With Icon")
ui.input_action_button(
"icon", "Button with icon", icon=icon_svg("trash-arrow-up")
)

@render.code
def icon_txt():
return f"Button clicked {input.icon()} times"

with ui.card():
ui.h3("Disabled Button")
ui.input_action_button("disabled", "Disabled button", disabled=True)

@render.code
def disabled_txt():
return f"Button clicked {input.disabled()} times"
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from playwright.sync_api import Page

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


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

default = controller.InputActionButton(page, "default")
default.expect_label("Default button")
controller.OutputCode(page, "default_txt").expect_value("Button clicked 0 times")
default.click()
controller.OutputCode(page, "default_txt").expect_value("Button clicked 1 times")

width = controller.InputActionButton(page, "width")
width.expect_width("200px")

disabled = controller.InputActionButton(page, "disabled")
disabled.expect_disabled()

# TODO-karan: test for icon
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from faicons import icon_svg

from shiny.express import input, render, ui

ui.page_opts(title="Kitchen Sink: ui.input_action_link()", fillable=True)

with ui.layout_columns():
with ui.card():
ui.input_action_link("default", label="Default action link")

@render.code
def default_txt():
return f"Link clicked {input.default()} times"

with ui.card():
ui.input_action_link("icon", "Link with icon", icon=icon_svg("trash-arrow-up"))

@render.code
def icon_txt():
return f"Link clicked {input.icon()} times"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from playwright.sync_api import Page

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


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

default = controller.InputActionLink(page, "default")
default.expect_label("Default action link")
controller.OutputCode(page, "default_txt").expect_value("Link clicked 0 times")
default.click()
controller.OutputCode(page, "default_txt").expect_value("Link clicked 1 times")

# TODO-karan: test for icon
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from shiny.express import input, render, ui

ui.page_opts(title="Kitchen Sink: ui.input_numeric()", fillable=True)

with ui.layout_columns():
with ui.card():
ui.h3("Default Numeric Input")
ui.input_numeric("default", label="Default numeric input", value=10)

@render.code
def default_txt():
return str(input.default())

with ui.card():
ui.h3("With Min and Max")
ui.input_numeric("min_max", "Min and Max", min=0, max=100, value=50)

@render.code
def min_max_txt():
return str(input.min_max())


with ui.layout_columns():
with ui.card():
ui.h3("With Step")
ui.input_numeric("step", "Step of 0.5", step=0.5, value=2.5)

@render.code
def step_txt():
return str(input.step())

with ui.card():
ui.h3("Custom Width")
ui.input_numeric("width", "Custom width", width="200px", value=15)

@render.code
def width_txt():
return str(input.width())
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from playwright.sync_api import Page

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


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

default = controller.InputNumeric(page, "default")
default.expect_label("Default numeric input")
default.expect_value("10")
controller.OutputUi(page, "default_txt").expect.to_have_text("10")

min_max = controller.InputNumeric(page, "min_max")
min_max.expect_max("100")
min_max.expect_min("0")
min_max.expect_value("50")
controller.OutputUi(page, "min_max_txt").expect.to_have_text("50")

step = controller.InputNumeric(page, "step")
step.expect_step("0.5")

width = controller.InputNumeric(page, "width")
width.expect_width("200px")
width.set("20")
width.expect_value("20")
controller.OutputUi(page, "width_txt").expect.to_have_text("20")
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from shiny.express import input, render, ui

ui.page_opts(title="Kitchen Sink: ui.input_password()", fillable=True)

with ui.layout_columns():
with ui.card():
ui.input_password("default", label="Default password input")

@render.code
def default_txt():
return str(input.default())

with ui.card():
ui.input_password(
"placeholder", "With placeholder", placeholder="Enter password"
)

@render.code
def placeholder_txt():
return str(input.placeholder())


with ui.layout_columns():
with ui.card():
ui.input_password("width", "Custom width", width="200px")

@render.code
def width_txt():
return str(input.width())

with ui.card():
ui.input_password("value", "With initial value", value="secret123")

@render.code
def value_txt():
return str(input.value())
Loading
Loading