Skip to content

Commit a9f7807

Browse files
authored
test(controller): Add test for controller documentation coverage (#2035)
1 parent 3055517 commit a9f7807

File tree

4 files changed

+130
-4
lines changed

4 files changed

+130
-4
lines changed

docs/_quartodoc-testing.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ quartodoc:
2626
contents:
2727
- playwright.controller.InputActionButton
2828
- playwright.controller.InputActionLink
29+
- playwright.controller.InputBookmarkButton
2930
- playwright.controller.InputCheckbox
3031
- playwright.controller.InputCheckboxGroup
3132
- playwright.controller.InputDarkMode
@@ -59,6 +60,7 @@ quartodoc:
5960
- playwright.controller.NavsetTab
6061
- playwright.controller.NavsetUnderline
6162
- playwright.controller.NavPanel
63+
- playwright.controller.PageNavbar
6264
- title: Upload and download
6365
desc: Methods for interacting with Shiny app uploading and downloading controller.
6466
contents:

shiny/playwright/controller/_output.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,13 +1058,33 @@ def expect_class_state(
10581058
"cell-edit-editing", timeout=timeout
10591059
)
10601060
elif value == "editing":
1061-
self.expect_cell_class("cell-edit-editing", row=row, col=col)
1061+
self.expect_cell_class(
1062+
"cell-edit-editing",
1063+
row=row,
1064+
col=col,
1065+
timeout=timeout,
1066+
)
10621067
elif value == "saving":
1063-
self.expect_cell_class("cell-edit-saving", row=row, col=col)
1068+
self.expect_cell_class(
1069+
"cell-edit-saving",
1070+
row=row,
1071+
col=col,
1072+
timeout=timeout,
1073+
)
10641074
elif value == "failure":
1065-
self.expect_cell_class("cell-edit-failure", row=row, col=col)
1075+
self.expect_cell_class(
1076+
"cell-edit-failure",
1077+
row=row,
1078+
col=col,
1079+
timeout=timeout,
1080+
)
10661081
elif value == "success":
1067-
self.expect_cell_class("cell-edit-success", row=row, col=col)
1082+
self.expect_cell_class(
1083+
"cell-edit-success",
1084+
row=row,
1085+
col=col,
1086+
timeout=timeout,
1087+
)
10681088
else:
10691089
raise ValueError(
10701090
"Invalid state. Select one of 'success', 'failure', 'saving', 'editing', 'ready'"

tests/playwright/shiny/components/data_frame/validate_row_selection_edit_mode/test_validate_row_selection_edit_mode.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ def test_validate_row_selection_in_edit_mode(
6060
page.keyboard.press("Escape")
6161
data_frame._edit_cell_no_save("Temp value", row=1, col=16)
6262
page.keyboard.press("Escape")
63+
# Wait for the row to be focused again after escaping edit mode
64+
data_frame._expect_row_focus_state(True, row=1)
65+
# Also ensure the cell is no longer in editing state
66+
data_frame.expect_class_state("ready", row=1, col=16)
6367
page.keyboard.press("Enter")
6468
data_frame.expect_class_state(
6569
"editing",
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
from __future__ import annotations
2+
3+
import ast
4+
from pathlib import Path
5+
from typing import Set
6+
7+
import pytest
8+
import yaml
9+
10+
root = Path(__file__).parent.parent.parent
11+
12+
CONTROLLER_DIR = root / "shiny/playwright/controller"
13+
DOCS_CONFIG = root / "docs/_quartodoc-testing.yml"
14+
SKIP_PATTERNS = {"Base", "Container", "Label", "StyleM"}
15+
CONTROLLER_BASE_PATTERNS = {
16+
"Base",
17+
"Container",
18+
"Label",
19+
"StyleM",
20+
"WidthLocM",
21+
"InputActionButton",
22+
"UiBase",
23+
"UiWithLabel",
24+
"UiWithContainer",
25+
}
26+
27+
28+
def _is_valid_controller_class(node: ast.ClassDef) -> bool:
29+
class_name = node.name
30+
base_names = {ast.unparse(base) for base in node.bases}
31+
32+
return (
33+
not class_name.startswith("_")
34+
and not any(pattern in class_name for pattern in SKIP_PATTERNS)
35+
and not any(base.endswith("P") for base in base_names if isinstance(base, str))
36+
and any(
37+
base.startswith("_") or any(p in base for p in CONTROLLER_BASE_PATTERNS)
38+
for base in base_names
39+
)
40+
)
41+
42+
43+
def get_controller_classes() -> Set[str]:
44+
classes: Set[str] = set()
45+
for py_file in CONTROLLER_DIR.glob("*.py"):
46+
if py_file.name == "__init__.py":
47+
continue
48+
try:
49+
tree = ast.parse(py_file.read_text(encoding="utf-8"))
50+
classes.update(
51+
node.name
52+
for node in ast.walk(tree)
53+
if isinstance(node, ast.ClassDef) and _is_valid_controller_class(node)
54+
)
55+
except Exception as e:
56+
pytest.fail(f"Failed to parse {py_file}: {e}")
57+
return classes
58+
59+
60+
def get_documented_controllers() -> Set[str]:
61+
try:
62+
config = yaml.safe_load(DOCS_CONFIG.read_text(encoding="utf-8"))
63+
except Exception as e:
64+
pytest.fail(f"Failed to load or parse {DOCS_CONFIG}: {e}")
65+
66+
return {
67+
content.split(".")[-1]
68+
for section in config.get("quartodoc", {}).get("sections", [])
69+
for content in section.get("contents", [])
70+
if isinstance(content, str) and content.startswith("playwright.controller.")
71+
}
72+
73+
74+
def test_all_controllers_are_documented():
75+
controller_classes = get_controller_classes()
76+
documented_controllers = get_documented_controllers()
77+
78+
missing_from_docs = controller_classes - documented_controllers
79+
extra_in_docs = documented_controllers - controller_classes
80+
81+
error_messages: list[str] = []
82+
if missing_from_docs:
83+
missing_list = "\n".join(
84+
sorted(f" - playwright.controller.{c}" for c in missing_from_docs)
85+
)
86+
error_messages.append(
87+
f"Controllers missing from {DOCS_CONFIG}:\n{missing_list}"
88+
)
89+
90+
if extra_in_docs:
91+
extra_list = "\n".join(
92+
sorted(f" - playwright.controller.{c}" for c in extra_in_docs)
93+
)
94+
error_messages.append(f"Extraneous classes in {DOCS_CONFIG}:\n{extra_list}")
95+
96+
if error_messages:
97+
pytest.fail("\n\n".join(error_messages), pytrace=False)
98+
99+
assert controller_classes, "No controller classes were found."
100+
assert documented_controllers, "No documented controllers were found."

0 commit comments

Comments
 (0)