Skip to content

Commit f9f29e7

Browse files
authored
Add busy indicator tests (#1391)
1 parent 7d5b616 commit f9f29e7

File tree

3 files changed

+137
-0
lines changed

3 files changed

+137
-0
lines changed

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ test =
6565
pytest>=6.2.4
6666
pytest-asyncio>=0.17.2
6767
pytest-playwright>=0.3.0
68+
playwright>=1.43.0
6869
pytest-xdist
6970
pytest-timeout
7071
pytest-rerunfailures
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# pyright:basic
2+
import time
3+
4+
import numpy as np
5+
import seaborn as sns
6+
7+
from shiny import App, module, reactive, render, ui
8+
9+
10+
# -- Reusable card module --
11+
@module.ui
12+
def card_ui(spinner_type, spinner_color, spinner_size):
13+
return ui.card(
14+
ui.busy_indicators.options(
15+
spinner_type=spinner_type,
16+
spinner_color=spinner_color,
17+
spinner_size=spinner_size,
18+
),
19+
ui.card_header("Spinner: " + spinner_type),
20+
ui.output_plot("plot"),
21+
)
22+
23+
24+
@module.server
25+
def card_server(input, output, session, rerender):
26+
@render.plot
27+
def plot():
28+
rerender()
29+
time.sleep(0.5)
30+
sns.lineplot(x=np.arange(100), y=np.random.randn(100))
31+
32+
33+
# -- Main app --
34+
app_ui = ui.page_fillable(
35+
ui.busy_indicators.options(
36+
pulse_background="linear-gradient(45deg, blue, red)",
37+
pulse_height="100px",
38+
pulse_speed="4s",
39+
),
40+
# ui.busy_indicators.use(spinners=False, pulse=True),
41+
ui.input_radio_buttons(
42+
"busy_indicator_type",
43+
"Choose the indicator type",
44+
["spinners", "pulse"],
45+
inline=True,
46+
),
47+
ui.input_task_button("rerender", "Re-render"),
48+
ui.layout_columns(
49+
card_ui("ring", "ring", "red", "10px"),
50+
card_ui("bars", "bars", "green", "20px"),
51+
card_ui("dots", "dots", "blue", "30px"),
52+
card_ui("pulse", "pulse", "olive", "50px"),
53+
col_widths=6,
54+
),
55+
ui.output_ui("indicator_types_ui"),
56+
)
57+
58+
59+
def server(input, output, session):
60+
61+
@reactive.calc
62+
@reactive.event(input.rerender, ignore_none=False)
63+
def rerender():
64+
return input.rerender()
65+
66+
card_server("ring", rerender=rerender)
67+
card_server("bars", rerender=rerender)
68+
card_server("dots", rerender=rerender)
69+
card_server("pulse", rerender=rerender)
70+
71+
@render.ui
72+
def indicator_types_ui():
73+
selected_busy_indicator_type = input.busy_indicator_type()
74+
return ui.busy_indicators.use(
75+
spinners=(selected_busy_indicator_type == "spinners"),
76+
pulse=(selected_busy_indicator_type != "spinners"),
77+
)
78+
79+
80+
app = App(app_ui, server, debug=True)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os
2+
from urllib.parse import urlparse
3+
4+
from conftest import ShinyAppProc
5+
from controls import InputRadioButtons, InputTaskButton, expect_not_to_have_class
6+
from playwright.sync_api import Page, expect
7+
8+
9+
def get_spinner_computed_property(
10+
page: Page, element_id: str, property_name: str
11+
) -> str:
12+
expect(page.locator(element_id)).to_be_visible()
13+
return page.evaluate(
14+
f"window.getComputedStyle(document.querySelector('{element_id}'), '::after').getPropertyValue('{property_name}');"
15+
)
16+
17+
18+
def get_pulse_computed_property(page: Page, property_name: str) -> str:
19+
expect(page.locator("html body")).to_be_visible()
20+
return page.evaluate(
21+
f"window.getComputedStyle(document.documentElement, '::after').getPropertyValue('{property_name}');"
22+
)
23+
24+
25+
def test_busy_indicators(page: Page, local_app: ShinyAppProc) -> None:
26+
page.goto(local_app.url)
27+
spinner_type = InputRadioButtons(page, "busy_indicator_type")
28+
render_button = InputTaskButton(page, "rerender")
29+
30+
# Verify spinner indicator behavior
31+
spinner_properties = [
32+
("#pulse-plot", "50px", "rgb(128, 128, 0)", "pulse.svg"),
33+
("#ring-plot", "10px", "rgb(255, 0, 0)", "ring.svg"),
34+
("#bars-plot", "20px", "rgb(0, 128, 0)", "bars.svg"),
35+
("#dots-plot", "30px", "rgb(0, 0, 255)", "dots.svg"),
36+
]
37+
38+
for element_id, height, background_color, svg_name in spinner_properties:
39+
assert get_spinner_computed_property(page, element_id, "height") == height
40+
assert (
41+
get_spinner_computed_property(page, element_id, "background-color")
42+
== background_color
43+
)
44+
mask_image_url = get_spinner_computed_property(page, element_id, "mask-image")
45+
assert os.path.basename(urlparse(mask_image_url).path).rstrip('")') == svg_name
46+
assert get_spinner_computed_property(page, element_id, "width") == height
47+
48+
# Verify pulse indicator behavior
49+
expect_not_to_have_class(page.locator("html"), "shiny-busy", timeout=8000)
50+
spinner_type.set("pulse")
51+
render_button.click()
52+
assert get_pulse_computed_property(page, "height") == "100px"
53+
assert (
54+
get_pulse_computed_property(page, "background-image")
55+
== "linear-gradient(45deg, rgb(0, 0, 255), rgb(255, 0, 0))"
56+
)

0 commit comments

Comments
 (0)