Skip to content

Commit a50c637

Browse files
Add E2E tests for accordion and autoresize (#601)
Co-authored-by: Barret Schloerke <[email protected]>
1 parent 2d5acbe commit a50c637

File tree

4 files changed

+250
-8
lines changed

4 files changed

+250
-8
lines changed

e2e/controls.py

Lines changed: 138 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,7 @@ def expect_locator_values_in_list(
938938
*,
939939
page: Page,
940940
loc_container: Locator,
941-
el_type: str,
941+
el_type: Locator | str,
942942
arr_name: str,
943943
arr: ListPatternOrStr,
944944
is_checked: bool | MISSING_TYPE = MISSING,
@@ -949,13 +949,20 @@ def expect_locator_values_in_list(
949949

950950
# Make sure the locator has len(uniq_arr) input elements
951951
_MultipleDomItems.assert_arr_is_unique(arr, f"`{arr_name}` must be unique")
952-
is_checked_str = _MultipleDomItems.checked_css_str(is_checked)
953952

954-
item_selector = f"{el_type}{is_checked_str}"
953+
if isinstance(el_type, Locator):
954+
if not isinstance(is_checked, MISSING_TYPE):
955+
raise RuntimeError(
956+
"`is_checked` cannot be specified if `el_type` is a Locator"
957+
)
958+
loc_item = el_type
959+
else:
960+
is_checked_str = _MultipleDomItems.checked_css_str(is_checked)
961+
loc_item = page.locator(f"{el_type}{is_checked_str}")
955962

956963
# If there are no items, then we should not have any elements
957964
if len(arr) == 0:
958-
playwright_expect(loc_container.locator(item_selector)).to_have_count(
965+
playwright_expect(loc_container.locator(el_type)).to_have_count(
959966
0, timeout=timeout
960967
)
961968
return
@@ -964,7 +971,7 @@ def expect_locator_values_in_list(
964971
# Find all items in set
965972
for item, i in zip(arr, range(len(arr))):
966973
# Get all elements of type
967-
has_locator = page.locator(item_selector)
974+
has_locator = loc_item
968975
# Get the `n`th matching element
969976
has_locator = has_locator.nth(i)
970977
# Make sure that element has the correct attribute value
@@ -982,7 +989,7 @@ def expect_locator_values_in_list(
982989
# Make sure other items are not in set
983990
# If we know all elements are contained in the container,
984991
# and all elements all unique, then it should have a count of `len(arr)`
985-
loc_inputs = loc_container.locator(item_selector)
992+
loc_inputs = loc_container.locator(loc_item)
986993
try:
987994
playwright_expect(loc_inputs).to_have_count(len(arr), timeout=timeout)
988995
except AssertionError as e:
@@ -992,14 +999,14 @@ def expect_locator_values_in_list(
992999
playwright_expect(loc_container_orig).to_have_count(1, timeout=timeout)
9931000

9941001
# Expecting the container to contain {len(arr)} items
995-
playwright_expect(loc_container_orig.locator(item_selector)).to_have_count(
1002+
playwright_expect(loc_container_orig.locator(loc_item)).to_have_count(
9961003
len(arr), timeout=timeout
9971004
)
9981005

9991006
for item, i in zip(arr, range(len(arr))):
10001007
# Expecting item `{i}` to be `{item}`
10011008
playwright_expect(
1002-
loc_container_orig.locator(item_selector).nth(i)
1009+
loc_container_orig.locator(loc_item).nth(i)
10031010
).to_have_attribute(key, item, timeout=timeout)
10041011

10051012
# Could not find the reason why. Raising the original error.
@@ -2096,6 +2103,7 @@ def expect_value(
20962103
*,
20972104
timeout: Timeout = None,
20982105
) -> None:
2106+
"""Note this function will trim value and output text value before comparing them"""
20992107
self.expect.to_have_text(value, timeout=timeout)
21002108

21012109

@@ -2565,3 +2573,125 @@ def expect_min_height(self, value: StyleValue, *, timeout: Timeout = None) -> No
25652573

25662574
def expect_height(self, value: StyleValue, *, timeout: Timeout = None) -> None:
25672575
expect_to_have_style(self.loc_container, "height", value, timeout=timeout)
2576+
2577+
2578+
### Experimental below
2579+
2580+
2581+
class Accordion(
2582+
_WidthLocM,
2583+
_InputWithContainer,
2584+
):
2585+
# *args: AccordionPanel | TagAttrs,
2586+
# id: Optional[str] = None,
2587+
# open: Optional[bool | str | list[str]] = None,
2588+
# multiple: bool = True,
2589+
# class_: Optional[str] = None,
2590+
# width: Optional[CssUnit] = None,
2591+
# height: Optional[CssUnit] = None,
2592+
# **kwargs: TagAttrValue,
2593+
def __init__(self, page: Page, id: str) -> None:
2594+
super().__init__(
2595+
page,
2596+
id=id,
2597+
loc="> div.accordion-item",
2598+
loc_container=f"div#{id}.accordion.shiny-bound-input",
2599+
)
2600+
self.loc_open = self.loc.locator(
2601+
# Return self
2602+
"xpath=.",
2603+
# Simple approach as position is not needed
2604+
has=page.locator(
2605+
"> div.accordion-collapse.show",
2606+
),
2607+
)
2608+
2609+
def expect_height(self, value: StyleValue, *, timeout: Timeout = None) -> None:
2610+
expect_to_have_style(self.loc_container, "height", value, timeout=timeout)
2611+
2612+
def expect_width(self, value: StyleValue, *, timeout: Timeout = None) -> None:
2613+
expect_to_have_style(self.loc_container, "width", value, timeout=timeout)
2614+
2615+
def expect_open(
2616+
self,
2617+
value: list[PatternOrStr],
2618+
*,
2619+
timeout: Timeout = None,
2620+
) -> None:
2621+
_MultipleDomItems.expect_locator_values_in_list(
2622+
page=self.page,
2623+
loc_container=self.loc_container,
2624+
el_type=self.page.locator(
2625+
"> div.accordion-item",
2626+
has=self.page.locator("> div.accordion-collapse.show"),
2627+
),
2628+
# el_type="> div.accordion-item:has(> div.accordion-collapse.show)",
2629+
arr_name="value",
2630+
arr=value,
2631+
key="data-value",
2632+
timeout=timeout,
2633+
)
2634+
2635+
def expect_panels(
2636+
self,
2637+
value: list[PatternOrStr],
2638+
*,
2639+
timeout: Timeout = None,
2640+
) -> None:
2641+
_MultipleDomItems.expect_locator_values_in_list(
2642+
page=self.page,
2643+
loc_container=self.loc_container,
2644+
el_type="> div.accordion-item",
2645+
arr_name="value",
2646+
arr=value,
2647+
key="data-value",
2648+
timeout=timeout,
2649+
)
2650+
2651+
def accordion_panel(
2652+
self,
2653+
data_value: str,
2654+
) -> AccordionPanel:
2655+
return AccordionPanel(self.page, self.id, data_value)
2656+
2657+
2658+
class AccordionPanel(
2659+
_WidthLocM,
2660+
_InputWithContainer,
2661+
):
2662+
# self,
2663+
# *args: TagChild | TagAttrs,
2664+
# data_value: str,
2665+
# icon: TagChild | None,
2666+
# title: TagChild | None,
2667+
# id: str | None,
2668+
# **kwargs: TagAttrValue,
2669+
def __init__(self, page: Page, id: str, data_value: str) -> None:
2670+
super().__init__(
2671+
page,
2672+
id=id,
2673+
loc=f"> div.accordion-item[data-value='{data_value}']",
2674+
loc_container=f"div#{id}.accordion.shiny-bound-input",
2675+
)
2676+
2677+
self.loc_label = self.loc.locator(
2678+
"> .accordion-header > .accordion-button > .accordion-title"
2679+
)
2680+
2681+
self.loc_icon = self.loc.locator(
2682+
"> .accordion-header > .accordion-button > .accordion-icon"
2683+
)
2684+
2685+
self.loc_body = self.loc.locator("> .accordion-collapse")
2686+
2687+
def expect_label(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
2688+
playwright_expect(self.loc_label).to_have_text(value, timeout=timeout)
2689+
2690+
def expect_body(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
2691+
playwright_expect(self.loc_body).to_have_text(value, timeout=timeout)
2692+
2693+
def expect_icon(self, value: PatternOrStr, *, timeout: Timeout = None) -> None:
2694+
playwright_expect(self.loc_icon).to_have_text(value, timeout=timeout)
2695+
2696+
def expect_open(self, is_open: bool, *, timeout: Timeout = None) -> None:
2697+
_expect_class_value(self.loc_body, "show", is_open, timeout=timeout)
File renamed without changes.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from conftest import ShinyAppProc
2+
from controls import Accordion, InputActionButton, OutputTextVerbatim
3+
from playwright.sync_api import Page
4+
5+
6+
def test_accordion(page: Page, local_app: ShinyAppProc) -> None:
7+
page.goto(local_app.url)
8+
9+
acc = Accordion(page, "acc")
10+
acc_panel_A = acc.accordion_panel("Section A")
11+
output_txt_verbatim = OutputTextVerbatim(page, "acc_txt")
12+
alternate_button = InputActionButton(page, "alternate")
13+
open_all_button = InputActionButton(page, "open_all")
14+
close_all_button = InputActionButton(page, "close_all")
15+
toggle_b_button = InputActionButton(page, "toggle_b")
16+
toggle_updates_button = InputActionButton(page, "toggle_updates")
17+
toggle_efg_button = InputActionButton(page, "toggle_efg")
18+
acc.expect_width(None)
19+
acc.expect_height(None)
20+
21+
# initial state - by default only A is open
22+
acc.expect_panels(["Section A", "Section B", "Section C", "Section D"])
23+
output_txt_verbatim.expect_value("input.acc(): ('Section A',)")
24+
acc.expect_open(["Section A"])
25+
acc_panel_A.expect_label("Section A")
26+
acc_panel_A.expect_body("Some narrative for section A")
27+
acc_panel_A.expect_open(True)
28+
29+
alternate_button.click()
30+
acc.expect_open(["Section B", "Section D"])
31+
output_txt_verbatim.expect_value("input.acc(): ('Section B', 'Section D')")
32+
33+
alternate_button.click()
34+
acc.expect_open(["Section A", "Section C"])
35+
output_txt_verbatim.expect_value("input.acc(): ('Section A', 'Section C')")
36+
37+
open_all_button.click()
38+
acc.expect_open(["Section A", "Section B", "Section C", "Section D"])
39+
output_txt_verbatim.expect_value(
40+
"input.acc(): ('Section A', 'Section B', 'Section C', 'Section D')"
41+
)
42+
43+
close_all_button.click()
44+
acc.expect_open([])
45+
output_txt_verbatim.expect_value("input.acc(): None")
46+
47+
toggle_b_button.click()
48+
acc.expect_open(["Section B"])
49+
output_txt_verbatim.expect_value("input.acc(): ('Section B',)")
50+
51+
acc_panel_updated_A = acc.accordion_panel("updated_section_a")
52+
toggle_updates_button.click()
53+
acc_panel_updated_A.expect_label("Updated title")
54+
acc_panel_updated_A.expect_body("Updated body")
55+
acc_panel_updated_A.expect_icon("Look! An icon! -->")
56+
57+
acc.expect_panels(["updated_section_a", "Section B", "Section C", "Section D"])
58+
output_txt_verbatim.expect_value("input.acc(): ('updated_section_a', 'Section B')")
59+
60+
toggle_efg_button.click()
61+
acc.expect_panels(
62+
[
63+
"updated_section_a",
64+
"Section B",
65+
"Section C",
66+
"Section D",
67+
"Section E",
68+
"Section F",
69+
"Section G",
70+
]
71+
)
72+
acc.expect_open(
73+
["updated_section_a", "Section B", "Section E", "Section F", "Section G"]
74+
)
75+
# will be uncommented once https://github.com/rstudio/bslib/issues/565 is fixed
76+
# output_txt_verbatim.expect_value(
77+
# "input.acc(): ('updated_section_a', 'Section B', 'Section E', 'Section F', 'Section G')"
78+
# )
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from conftest import ShinyAppProc, x_create_doc_example_fixture
2+
from controls import InputTextArea, OutputTextVerbatim
3+
from playwright.sync_api import Locator, Page
4+
5+
app = x_create_doc_example_fixture("input_text_area")
6+
7+
resize_number = 6
8+
9+
10+
def get_box_height(locator: Locator) -> float:
11+
bounding_box = locator.bounding_box()
12+
if bounding_box is not None:
13+
return bounding_box["height"]
14+
else:
15+
return 0
16+
17+
18+
def test_autoresize(page: Page, app: ShinyAppProc) -> None:
19+
page.goto(app.url)
20+
21+
input_area = InputTextArea(page, "caption")
22+
output_txt_verbatim = OutputTextVerbatim(page, "value")
23+
input_area.expect_height(None)
24+
input_area.expect_width(None)
25+
input_area.set("test value")
26+
# use bounding box approach since height is dynamic
27+
initial_height = get_box_height(input_area.loc)
28+
output_txt_verbatim.expect_value("test value")
29+
for _ in range(resize_number):
30+
input_area.loc.press("Enter")
31+
input_area.loc.type("end value")
32+
return_txt = "\n" * resize_number
33+
output_txt_verbatim.expect_value(f"test value{return_txt}end value")
34+
assert get_box_height(input_area.loc) > initial_height

0 commit comments

Comments
 (0)