Skip to content

Commit 19a888d

Browse files
committed
Add tests along with core & express apps
1 parent e1d4a50 commit 19a888d

File tree

62 files changed

+3164
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+3164
-3
lines changed

shiny/playwright/controller/_base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,12 @@ def set_text(
123123

124124

125125
def _expect_multiple(loc: Locator, multiple: bool, timeout: Timeout = None) -> None:
126-
value = "True" if multiple else None
127-
_expect_style_to_have_value(loc, "multiple", value, timeout=timeout)
126+
_expect_attribute_to_have_value(
127+
loc,
128+
"multiple",
129+
value="multiple" if multiple else None,
130+
timeout=timeout,
131+
)
128132

129133

130134
######################################################

shiny/playwright/controller/_input_buttons.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def click(self, *, timeout: Timeout = None):
101101
timeout
102102
The maximum time to wait for the input dark mode to be clicked. Defaults to `None`.
103103
"""
104-
self.loc.click(timeout=timeout)
104+
self.loc.locator("button").click(timeout=timeout)
105105
return self
106106

107107
def expect_mode(self, value: str, *, timeout: Timeout = None):
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from shiny import App, reactive, render, ui
2+
3+
# Define the UI
4+
app_ui = ui.page_fillable(
5+
# Add Font Awesome CSS in the head section
6+
ui.tags.head(
7+
ui.tags.link(
8+
rel="stylesheet",
9+
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css",
10+
)
11+
),
12+
# Card containing accordion
13+
ui.card(
14+
ui.accordion(
15+
# Basic Panel
16+
ui.accordion_panel(
17+
"Basic Panel",
18+
ui.markdown("This is a basic panel with just a title parameter"),
19+
),
20+
# Panel with title and value
21+
ui.accordion_panel(
22+
"Panel with Value",
23+
ui.markdown("This panel has both a title and a value parameter"),
24+
value="panel2",
25+
),
26+
# Panel with title, value, and icon
27+
ui.accordion_panel(
28+
"Panel with Icon",
29+
ui.markdown("This panel includes an icon parameter using Font Awesome"),
30+
value="panel3",
31+
icon=ui.tags.i(
32+
class_="fa-solid fa-shield-halved", style="font-size: 1rem;"
33+
),
34+
),
35+
# Panel with title, value, icon, and custom attributes
36+
ui.accordion_panel(
37+
"Panel with Custom Attributes",
38+
ui.markdown(
39+
"This panel demonstrates custom attributes (class and style)"
40+
),
41+
value="panel4",
42+
icon=ui.tags.i(class_="fa-solid fa-star", style="font-size: 1rem;"),
43+
class_="custom-panel",
44+
style="background-color: #f8f9fa;",
45+
),
46+
id="acc",
47+
open=True,
48+
multiple=True,
49+
),
50+
),
51+
# Output for selected panel
52+
ui.output_text("selected_panel"),
53+
)
54+
55+
56+
# Define the server
57+
def server(input, output, session):
58+
@output
59+
@render.text
60+
def selected_panel():
61+
return f"Currently selected panel: {input.acc()}"
62+
63+
64+
# Create the app
65+
app = App(app_ui, server)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from shiny import reactive
2+
from shiny.express import input, ui, render
3+
4+
# Add Font Awesome CSS in the head section first
5+
ui.head_content(
6+
ui.HTML(
7+
'<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">'
8+
)
9+
)
10+
11+
ui.page_opts(fillable=True)
12+
13+
with ui.card():
14+
with ui.accordion(id="acc", open=True, multiple=True):
15+
# Panel with just title
16+
with ui.accordion_panel("Basic Panel"):
17+
ui.markdown("This is a basic panel with just a title parameter")
18+
19+
# Panel with title and value
20+
with ui.accordion_panel("Panel with Value", value="panel2"):
21+
ui.markdown("This panel has both a title and a value parameter")
22+
23+
# Panel with title, value, and icon
24+
with ui.accordion_panel(
25+
"Panel with Icon",
26+
value="panel3",
27+
icon=ui.tags.i(
28+
class_="fa-solid fa-shield-halved", style="font-size: 1rem;"
29+
),
30+
):
31+
ui.markdown("This panel includes an icon parameter using Font Awesome")
32+
33+
# Panel with title, value, icon, and custom attributes
34+
with ui.accordion_panel(
35+
"Panel with Custom Attributes",
36+
value="panel4",
37+
icon=ui.tags.i(class_="fa-solid fa-star", style="font-size: 1rem;"),
38+
class_="custom-panel",
39+
style="background-color: #f8f9fa;",
40+
):
41+
ui.markdown("This panel demonstrates custom attributes (class and style)")
42+
43+
44+
# Show which panel is currently selected
45+
@render.text
46+
def selected_panel():
47+
return f"Currently selected panel: {input.acc()}"
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from playwright.sync_api import Page
2+
3+
from shiny.playwright import controller
4+
from shiny.pytest import create_app_fixture
5+
from shiny.run import ShinyAppProc
6+
7+
app = create_app_fixture(["app-core.py", "app-express.py"])
8+
9+
def test_accordion_demo(page: Page, app: ShinyAppProc) -> None:
10+
page.goto(app.url)
11+
12+
# Test accordion with ID "acc"
13+
accordion = controller.Accordion(page, "acc")
14+
15+
# Test that multiple panels can be open
16+
accordion.expect_multiple(True)
17+
18+
# Test that accordion is initially open
19+
panel1 = accordion.accordion_panel("Basic Panel")
20+
panel2 = accordion.accordion_panel("panel2")
21+
panel3 = accordion.accordion_panel("panel3")
22+
panel4 = accordion.accordion_panel("panel4")
23+
24+
# Test initial state
25+
panel1.expect_open(True)
26+
panel2.expect_open(True)
27+
panel3.expect_open(True)
28+
panel4.expect_open(True)
29+
30+
# Test panel labels
31+
panel1.expect_label("Basic Panel")
32+
panel2.expect_label("Panel with Value")
33+
panel3.expect_label("Panel with Icon")
34+
panel4.expect_label("Panel with Custom Attributes")
35+
36+
# Test panel content
37+
panel1.expect_body("This is a basic panel with just a title parameter")
38+
panel2.expect_body("This panel has both a title and a value parameter")
39+
panel3.expect_body("This panel includes an icon parameter using Font Awesome")
40+
panel4.expect_body("This panel demonstrates custom attributes (class and style)")
41+
42+
# Test icons (presence/absence)
43+
panel1.expect_icon(False) # First panel has no icon
44+
panel2.expect_icon(False) # Second panel has no icon
45+
panel3.expect_icon(True) # Third panel has an icon
46+
panel4.expect_icon(True) # Fourth panel has an icon
47+
48+
# Test closing and opening panels
49+
panel1.set(False)
50+
panel1.expect_open(False)
51+
panel1.set(True)
52+
panel1.expect_open(True)
53+
54+
# Test output text that shows selected panel
55+
selected_text = controller.OutputText(page, "selected_panel")
56+
selected_text.expect_value(
57+
"Currently selected panel: ('Basic Panel', 'panel2', 'panel3', 'panel4')"
58+
)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from shiny import App, reactive, render, ui
2+
3+
# Define the UI
4+
app_ui = ui.page_fillable(
5+
# Card with all possible parameters
6+
ui.card(
7+
ui.card_header("Card Demo", "This demonstrates all card parameters"),
8+
ui.markdown(
9+
"""
10+
This is the main content of the card.
11+
The card has various parameters set including:
12+
13+
* full_screen=True - allows expanding to full screen
14+
* height='300px' - sets fixed height
15+
* fill=True - allows card to grow/shrink
16+
* class_='my-4' - adds custom CSS classes
17+
"""
18+
),
19+
ui.card_footer("Card Footer", class_="text-muted"),
20+
id="demo_card",
21+
full_screen=True, # Allow card to be expanded to full screen
22+
height="300px", # Set card height
23+
fill=True, # Allow card to grow/shrink to fit container
24+
class_="my-4", # Add custom CSS classes
25+
),
26+
# Another card showing dynamic content
27+
ui.card(
28+
ui.card_header("Dynamic Content Demo"),
29+
ui.output_text("dynamic_content"),
30+
id="dynamic_card",
31+
full_screen=True,
32+
height="200px",
33+
class_="mt-4",
34+
),
35+
)
36+
37+
38+
# Define the server
39+
def server(input, output, session):
40+
@render.text
41+
def dynamic_content():
42+
return "This card shows how to include dynamic content using render functions"
43+
44+
45+
# Create the app
46+
app = App(app_ui, server)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from shiny import reactive
2+
from shiny.express import input, ui, render
3+
4+
# Set page options
5+
ui.page_opts(fillable=True)
6+
7+
# Card with all possible parameters
8+
with ui.card(
9+
id="demo_card",
10+
full_screen=True, # Allow card to be expanded to full screen
11+
height="300px", # Set card height
12+
fill=True, # Allow card to grow/shrink to fit container
13+
class_="my-4", # Add custom CSS classes
14+
):
15+
# Card header
16+
ui.card_header("Card Demo", "This demonstrates all card parameters")
17+
18+
# Card body content
19+
ui.markdown(
20+
"""
21+
This is the main content of the card.
22+
The card has various parameters set including:
23+
24+
* full_screen=True - allows expanding to full screen
25+
* height='300px' - sets fixed height
26+
* fill=True - allows card to grow/shrink
27+
* class_='my-4' - adds custom CSS classes
28+
"""
29+
)
30+
31+
# Card footer
32+
ui.card_footer("Card Footer", class_="text-muted")
33+
34+
# Another card showing dynamic content
35+
with ui.card(id="dynamic_card", full_screen=True, height="200px", class_="mt-4"):
36+
ui.card_header("Dynamic Content Demo")
37+
38+
@render.text
39+
def dynamic_content():
40+
return "This card shows how to include dynamic content using render functions"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from playwright.sync_api import Page
2+
from shiny.playwright import controller
3+
from shiny.run import ShinyAppProc
4+
from shiny.pytest import create_app_fixture
5+
6+
7+
app = create_app_fixture(["app-core.py", "app-express.py"])
8+
9+
10+
def test_cards(page: Page, app: ShinyAppProc) -> None:
11+
page.goto(app.url)
12+
# Test demo_card
13+
demo_card = controller.Card(page, "demo_card")
14+
demo_card.expect_header("Card DemoThis demonstrates all card parameters")
15+
demo_card.expect_height("300px")
16+
demo_card.expect_footer("Card Footer")
17+
demo_card.expect_full_screen_available(True)
18+
19+
# Test dynamic_card
20+
dynamic_card = controller.Card(page, "dynamic_card")
21+
dynamic_card.expect_header("Dynamic Content Demo")
22+
dynamic_card.expect_height("200px")
23+
dynamic_card.expect_full_screen_available(True)
24+
25+
# Test dynamic content in dynamic_card
26+
dynamic_content = controller.OutputText(page, "dynamic_content")
27+
dynamic_content.expect_value(
28+
"This card shows how to include dynamic content using render functions"
29+
)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from shiny import App, render, reactive, ui
2+
3+
app_ui = ui.page_fillable(
4+
# First card with basic footer
5+
ui.card(
6+
ui.card_header("Basic Card with Header"),
7+
"This is a basic card with header and footer",
8+
ui.card_footer("Footer content", class_="bg-light"),
9+
full_screen=True,
10+
height="300px",
11+
id="card1",
12+
),
13+
# Second card with complex footer
14+
ui.card(
15+
ui.card_header("Card with Complex Footer"),
16+
"This card shows different types of content in footer",
17+
ui.card_footer(
18+
ui.HTML("<strong>Bold text</strong>"),
19+
ui.tags.span(" | ", style="margin: 0 10px;"),
20+
ui.tags.em("Emphasized text"),
21+
ui.tags.span(" | ", style="margin: 0 10px;"),
22+
ui.tags.span("Regular text", style="color: green;"),
23+
),
24+
full_screen=True,
25+
height="600px",
26+
id="card2",
27+
),
28+
# Third card with interactive footer
29+
ui.card(
30+
ui.card_header("Card with Interactive Footer"),
31+
"This card has interactive elements in its footer",
32+
ui.card_footer(
33+
ui.input_action_button("btn", "Click Me", class_="btn-primary"),
34+
ui.output_text("click_count"),
35+
class_="d-flex justify-content-between align-items-center",
36+
),
37+
full_screen=True,
38+
height="400px",
39+
id="card3",
40+
),
41+
)
42+
43+
44+
def server(input, output, session):
45+
@output
46+
@render.text
47+
def click_count():
48+
if not input.btn():
49+
return "No clicks yet"
50+
return f"Clicked {input.btn()} times"
51+
52+
53+
app = App(app_ui, server)

0 commit comments

Comments
 (0)