Skip to content

Commit 83568a2

Browse files
tests(dataframe): Add additional tests for dataframe (#1487)
Co-authored-by: Barret Schloerke <[email protected]>
1 parent d1e19b9 commit 83568a2

File tree

5 files changed

+191
-3
lines changed

5 files changed

+191
-3
lines changed

shiny/_template_utils.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,7 @@ def {test_name}(page: Page, app: ShinyAppProc):
430430

431431
# Write template to test file
432432
test_file.write_text(template)
433+
434+
# next steps
435+
print("\nNext steps:")
436+
print("- Run `pytest` in your terminal to run all the tests")

shiny/playwright/controller/_controls.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5747,6 +5747,7 @@ def cell_locator(self, row: int, col: int) -> Locator:
57475747
.nth(col)
57485748
)
57495749

5750+
# TODO-barret; Should this be called `expect_row_count()`?
57505751
def expect_n_row(self, value: int, *, timeout: Timeout = None):
57515752
"""
57525753
Expects the number of rows in the data frame.
@@ -5762,6 +5763,92 @@ def expect_n_row(self, value: int, *, timeout: Timeout = None):
57625763
value, timeout=timeout
57635764
)
57645765

5766+
def expect_selected_n_row(self, value: int, *, timeout: Timeout = None):
5767+
"""
5768+
Expects the number of selected rows in the data frame.
5769+
5770+
Parameters
5771+
----------
5772+
value
5773+
The expected number of selected rows.
5774+
timeout
5775+
The maximum time to wait for the expectation to pass. Defaults to `None`.
5776+
"""
5777+
playwright_expect(
5778+
self.loc_body.locator("tr[aria-selected=true]")
5779+
).to_have_count(value, timeout=timeout)
5780+
5781+
def expect_selected_rows(self, rows: list[int], *, timeout: Timeout = None):
5782+
"""
5783+
Expects the specified rows to be selected.
5784+
5785+
Parameters
5786+
----------
5787+
rows
5788+
The row numbers.
5789+
timeout
5790+
The maximum time to wait for the expectation to pass. Defaults to `None`.
5791+
"""
5792+
# * given container...
5793+
# * Add that container has all known rows
5794+
# * Verify that selected row count is of size N
5795+
big_loc = self.loc_body
5796+
assert len(rows) > 0
5797+
for row in rows:
5798+
big_loc = big_loc.locator(
5799+
"xpath=.", # return "self"
5800+
has=self.page.locator(
5801+
f"> tr[data-index='{row}'][aria-selected='true']"
5802+
),
5803+
)
5804+
5805+
try:
5806+
playwright_expect(
5807+
big_loc.locator("> tr[aria-selected='true']")
5808+
).to_have_count(len(rows), timeout=timeout)
5809+
except AssertionError as e:
5810+
# Debug expections
5811+
5812+
# Expecting container to exist (count = 1)
5813+
playwright_expect(self.loc_body).to_have_count(1, timeout=timeout)
5814+
5815+
for row in rows:
5816+
# Expecting item `{item}` to exist in container
5817+
# Perform exact matches on strings.
5818+
playwright_expect(
5819+
# Simple approach as position is not needed
5820+
self.loc_body.locator(
5821+
f"> tr[aria-selected='true'][data-index='{row}']",
5822+
)
5823+
).to_have_count(1, timeout=timeout)
5824+
5825+
# Could not find the reason why. Raising the original error.
5826+
raise e
5827+
5828+
def expect_row_focus_state(
5829+
self, in_focus: bool = True, *, row: int, timeout: Timeout = None
5830+
):
5831+
"""
5832+
Expects the focus state of the specified row.
5833+
5834+
Parameters
5835+
----------
5836+
row
5837+
The row number.
5838+
in_focus
5839+
`True` if the row is expected to be in focus, `False` otherwise.
5840+
timeout
5841+
The maximum time to wait for the expectation to pass. Defaults to `None`.
5842+
"""
5843+
if in_focus:
5844+
playwright_expect(
5845+
self.loc_body.locator(f"> tr[data-index='{row}']")
5846+
).to_be_focused(timeout=timeout)
5847+
else:
5848+
playwright_expect(
5849+
self.loc_body.locator(f"> tr[data-index='{row}']")
5850+
).not_to_be_focused(timeout=timeout)
5851+
57655852
def expect_cell(
57665853
self,
57675854
value: PatternOrStr,

tests/playwright/shiny/components/data_frame/edit/app.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
# pyright: reportMissingTypeStubs = false
55
# pyright: reportArgumentType = false
66
# pyright: reportUnknownMemberType = false
7+
#
78
# TODO-barret-render.data_frame; Make an example that uses a dataframe that then updates a higher level reactive, that causes the df to update... which causes the table to render completely
89
# TODO-barret-render.data_frame; When "updating" data, try to maintain the scroll, filter info when a new `df` is supplied;
10+
#
911
# TODO-karan-test; Click outside the table. Tab to the column name, hit enter. Verify the table becomes sorted. Tab to an HTML column name, hit enter. Verify the sort does not update.
10-
# TODO-karan-test; Enable rows selection and editable. Select (and verify) a row. Edit a cell content in that row. Verify the row is not focused. Hit escape key. Verify the cell value is not updated. Verify the row is focused. Hit escape key again. Verify the row is not focused. (Possibly verify the container div is focused?)
11-
# TODO-karan-test; Enable rows selection and editable. Select (and verify) a row. Edit a cell content in that row. Click a cell in another row. Verify the new row is selected and focused. Verify the old row is not selected. Verify the old row cell value was updated.
12-
# TODO-karan-test; Enable rows selection and editable. Select (and verify) a row. Hit enter to edit the first cell in that row. Hit escape key. Verify the same row is focused. Scroll right and display an html column in the left part of the view. Hit enter to edit the first visible non-html cell in that row. Verify that cell is editing.
12+
#
1313
# TODO-future; Can we maintain pre-processed value and use it within editing?
1414
# A: Doesn't seem possible for now
1515
import great_tables as gt
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from palmerpenguins import load_penguins_raw # pyright: ignore[reportMissingTypeStubs]
2+
3+
from shiny import App, Inputs, Outputs, Session, render, ui
4+
5+
df = load_penguins_raw()
6+
7+
df["Species"] = df["Species"].apply(lambda x: ui.HTML(f"<u>{x}</u>")) # pyright: ignore
8+
9+
10+
app_ui = ui.page_fluid(
11+
ui.h2("Palmer Penguins"),
12+
ui.output_data_frame("penguins_df"),
13+
)
14+
15+
16+
def server(input: Inputs, output: Outputs, session: Session) -> None:
17+
@render.data_frame
18+
def penguins_df():
19+
return render.DataGrid(
20+
data=df, # pyright: ignore[reportUnknownArgumentType]
21+
editable=True,
22+
selection_mode="rows",
23+
)
24+
25+
26+
app = App(app_ui, server)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from playwright.sync_api import Page
2+
3+
from shiny.playwright import controller
4+
from shiny.run import ShinyAppProc
5+
6+
7+
def test_validate_row_selection_in_edit_mode(
8+
page: Page, local_app: ShinyAppProc
9+
) -> None:
10+
page.goto(local_app.url)
11+
12+
# Select (and verify) a row. Edit a cell content in that row.
13+
# Verify the row is not focused. Hit escape key. Verify the cell value is not updated.
14+
# Verify the row is focused. Hit escape key again.
15+
# Verify the row is not focused. (Possibly verify the container div is focused?)
16+
data_frame = controller.OutputDataFrame(page, "penguins_df")
17+
18+
data_frame.expect_cell("N1A2", row=1, col=6)
19+
data_frame.edit_cell("N2A2", row=1, col=6)
20+
data_frame.expect_row_focus_state(False, row=1)
21+
data_frame.expect_class_state("editing", row=1, col=6)
22+
data_frame.expect_selected_n_row(1)
23+
data_frame.expect_selected_rows([1])
24+
data_frame.save_cell("N3A2", row=1, col=6, save_key="Escape")
25+
data_frame.expect_cell("N1A2", row=1, col=6)
26+
data_frame.expect_row_focus_state(True, row=1)
27+
page.keyboard.press("Escape")
28+
data_frame.expect_row_focus_state(False, row=1)
29+
30+
# Enable rows selection and editable.
31+
# Select (and verify) a row. Edit a cell content in that row.
32+
# Click a cell in another row. Verify the new row is selected and focused.
33+
# Verify the old row is not selected. Verify the old row cell value was updated.
34+
data_frame.expect_cell("N1A2", row=1, col=6)
35+
data_frame.edit_cell("N2A2", row=1, col=6)
36+
data_frame.expect_row_focus_state(False, row=1)
37+
data_frame.expect_class_state("editing", row=1, col=6)
38+
data_frame.cell_locator(row=2, col=6).click()
39+
data_frame.expect_row_focus_state(True, row=2)
40+
data_frame.expect_row_focus_state(False, row=1)
41+
data_frame.expect_cell("N2A2", row=1, col=6)
42+
43+
# Enable rows selection and editable.
44+
# Select (and verify) a row. Hit enter to edit the first cell in that row.
45+
# Hit escape key. Verify the same row is focused.
46+
# Scroll right and display an html column in the left part of the view.
47+
# Hit enter to edit the first visible non-html cell in that row.
48+
# Verify that cell is editing.
49+
data_frame.cell_locator(row=1, col=2).click()
50+
page.keyboard.press("Enter")
51+
data_frame.expect_row_focus_state(False, row=1)
52+
page.keyboard.press("Escape")
53+
data_frame.expect_row_focus_state(True, row=1)
54+
page.keyboard.press("Escape")
55+
data_frame.edit_cell("Temp value", row=1, col=16)
56+
page.keyboard.press("Escape")
57+
page.keyboard.press("Enter")
58+
data_frame.expect_class_state("editing", row=1, col=0)
59+
60+
# Click outside the table/Press Escape to exit row focus.
61+
# Tab to the column name, hit enter. Verify the table becomes sorted.
62+
# Tab to an HTML column name, hit enter. Verify the sort does not update.
63+
page.keyboard.press("Escape")
64+
page.keyboard.press("Escape")
65+
page.keyboard.press("Tab")
66+
page.keyboard.press("Tab") # tab to sample number
67+
page.keyboard.press("Enter")
68+
data_frame.expect_cell("152", row=0, col=1)
69+
page.keyboard.press("Tab")
70+
page.keyboard.press("Enter")
71+
data_frame.expect_cell("Adelie Penguin (Pygoscelis adeliae)", row=0, col=2)

0 commit comments

Comments
 (0)